Delphi - Может ли TThread изменить значение переменной в основном потоке VCL?

Использование: Delphi XE2, приложение Windows VCL Forms

Может ли TThread во время выполнения изменить значение переменной в основном потоке VCL?

Необходимо обновить целое число, объявленное как поле класса TForm. Он будет передан в TThread как переменная var в перегруженном (и повторно введенном) методе конструктора Create.

Есть ли в этом запасные варианты?


person Steve F    schedule 04.02.2015    source источник
comment
По этой теме есть буквально тысячи статей, вопросов и руководств. Что вы уже нашли? Ваш подход к передаче этой переменной в ваш поток неверен. Вам необходимо синхронизировать события, чтобы изменить значение, или другие подходы, такие как использование сообщений.   -  person Jerry Dodge    schedule 04.02.2015
comment
Я также не уверен, как передача параметра var в конструктор потока позволит потоку обновлять эту переменную (кроме как внутри конструктора). Для этого вам понадобится указатель на это целое число, что по-прежнему небезопасно, поскольку эта переменная является частью VCL.   -  person Jerry Dodge    schedule 04.02.2015
comment
возможный дубликат Thread-safe в delphi   -  person Jerry Dodge    schedule 04.02.2015
comment
@JerryDodge, если int - это просто поле в форме, то технически это не VCL - это просто поле класса. Критическая секция или другой объект синхронизации подойдут.   -  person J...    schedule 04.02.2015
comment
Не используйте повторно ввести: 1) Функция была добавлена ​​для исключительно узкого сценария использования. И даже в этом случае лучше приложить все усилия, чтобы не скрывать метод предка, а не скрывать факт наличия проблемы. 2) В вашем конкретном случае вашей теме необходимо предоставить всю необходимую информацию для выполнения своей работы. TThread.Create не виртуальный. Так что лучше объявить единый стандарт акций constructor Create(<required params>); и все. Напишите свой конструктор потоков, в котором есть все, что ему нужно: ни больше, ни меньше. Так будет намного проще поддерживать.   -  person Disillusioned    schedule 04.02.2015


Ответы (1)


Да, потоки могут изменять переменные. Переменные не принадлежат потокам. Переменные могут принадлежать объектам формы или потока, но объект потока (т. Е. Экземпляр TThread или его потомков) отличается от потока выполнения ОС.

У объектов может быть код, который выполняется в нескольких потоках. Ваш метод TThread.Create выполняется в контексте вызывающего его потока, который часто является вашим основным потоком. С другой стороны, метод Execute выполняется в контексте созданного потока ОС. Но очевидно, что оба метода могут обращаться к полям объекта TThread, так что это отвечает на вопрос, могут ли два потока ОС обращаться к одной и той же переменной.

Однако у вас возникнут проблемы с доступом к переменной формы описанным вами способом. Передача его в конструктор в качестве параметра var позволит конструктору изменить его, но, как я упоминал выше, конструктор не запускается в контексте нового потока ОС. Чтобы новый поток мог получить доступ к этой переменной, вам нужно сохранить указатель на нее, а не передавать по ссылке. Например:

type
  TSteveThread = class(TThread)
  private
    FVariable: PInteger;
  protected
    procedure Execute; override;
  public
    constructor Create(Variable: PInteger);
  end;

constructor TSteveThread.Create;
begin
  inherited Create(False);
  FVariable := Variable;
end;

procedure TSteveThread.Execute;
begin
  // Access FVariable^ here.
end;

Создайте это так:

procedure TSteveForm.ButtonClick;
begin
  TSteveThread.Create(@Self.Variable);
end;

Альтернативой является передача вместо этого ссылки на форму, а затем доступ к полю формы через эту ссылку. Например:

type
  TSteveThread = class(TThread)
  private
    FForm: TSteveForm;
  protected
    procedure Execute; override;
  public
    constructor Create(Form: TSteveForm);
  end;

constructor TSteveThread.Create;
begin
  inherited Create(False);
  FForm := Form;
end;

procedure TSteveThread.Execute;
begin
  // Access FForm.Variable here.
end;

Создайте это так:

procedure TSteveForm.ButtonClick;
begin
  TSteveThread.Create(Self);
end;

В любом случае вам необходимо принять обычные меры предосторожности при управлении одновременным доступом к данным несколькими потоками. Суть в том, что оба потока могут получить доступ к данным.

person Rob Kennedy    schedule 04.02.2015
comment
Меня на секунду сбило с толку, как в объявлении Create был параметр, а в части Implementation - нет. Потом я вспомнил, хоть это и сбивает с толку, но это правильный код. Чтобы избежать путаницы, я всегда делаю подпись Implementation полностью совпадающей с объявленной в объекте. - person Jerry Dodge; 04.02.2015
comment
Разве ваше второе альтернативное решение не нарушает правила? Это пример того, как этого не делать. - person Jerry Dodge; 04.02.2015
comment
Я тоже, @Jerry, но я набираю это на телефоне, поэтому не хочу беспокоиться о большем, чем мне нужно. - person Rob Kennedy; 04.02.2015
comment
Я не вижу нарушения правил. [Нужна цитата]. - person Rob Kennedy; 04.02.2015
comment
Это рабочий поток, который вносит прямые изменения в основной поток VCL без какой-либо защиты. Вот что вызывает состояние (я) гонки. На самом деле в первом примере тоже нет защиты, например критического раздела. - person Jerry Dodge; 04.02.2015
comment
Это состояние гонки только в том случае, если оба потока могут использовать его одновременно без каких-либо других соображений. Чтобы избежать этого, можно использовать критические разделы, взаимосвязанные операции и любое количество других контекстно-зависимых методов, о чем упоминается в моем последнем абзаце. Мои два примера в этом отношении ничем не отличаются, @Jerry. - person Rob Kennedy; 04.02.2015
comment
Ваш последний абзац касается изменения указателей на значения, а не самих значений. Но два потока, обращающихся к одному и тому же блоку памяти одновременно, не адресуются. VCL может изменять эти переменные без вашего ведома, что может привести к потенциальным условиям гонки. Вот почему настоятельно рекомендуется синхронизировать обновления VCL. - person Jerry Dodge; 04.02.2015
comment
На самом деле в последнем абзаце я имел в виду ваш третий абзац до того, как начался код, но потом я увидел самый последний абзац. Я думаю, что OP спрашивает, как защитить эти переменные. Передача целого числа в качестве параметра var - это еще одна проблема, о которой OP, вероятно, никогда не задумывался. - person Jerry Dodge; 04.02.2015
comment
Когда я говорю о данных, @Jerry, это блок памяти, о котором вы беспокоитесь. Мы можем изменять значение FVariable по желанию, и ничто не будет заботиться, потому что мы можем безопасно предположить (или определить), что только объект потока, которому он принадлежит, не увидит его, но для доступа к FVariable^ - общим данным, на которые указывает указатель, - требуется предосторожность. - person Rob Kennedy; 04.02.2015
comment
Я считаю, что у OP две проблемы, и каждый из нас смотрит на нее по-своему. Я интерпретировал вопрос как безопасное совместное использование переменной VCL с другим потоком, в то время как вы интерпретировали его как передачу переменной VCL в поток. Поэтому я считаю, что самый последний абзац - единственная часть ответа, относящаяся к тому, что искал OP (хотя остальное тоже полезно). - person Jerry Dodge; 04.02.2015
comment
@Jerry, я видел вопрос: вот как я планирую предоставить доступ к переменной, но даже если это сработает, я не уверен, что это действительно можно изменить. Я ответил, что его можно изменить, но предлагаемый способ передачи не сработает. Я решил не вдаваться в подробности второго вопроса (о запасных шагах), потому что считаю, что он слишком широк, чтобы разбираться в главном вопросе. - person Rob Kennedy; 04.02.2015
comment
Вот почему я проголосовал за закрытие как дубликат, а не за ответ :-) Ответы на расплывчатые вопросы или вопросы с несколькими проблемами всегда приводят к такого рода вещам. Повторно назначил свой +1. - person Jerry Dodge; 04.02.2015
comment
Указатель с критическими секциями был очевидным решением. Ответ принят. - person Steve F; 27.05.2016