Асинхронная запись Delphi в TListBox

Я хочу писать из нескольких потоков/процессов в TListBox с именем listMessages, и для этого у меня есть две процедуры:

1- С добавлением объекта:

procedure Log(Msg: String; Color: TColor);
begin
  listMessages.Items.AddObject(Msg, Pointer(Color));
  listMessages.ItemIndex := listMessages.Items.Count -1;
end;

2- С TIdCriticalSection с именем protectListMessages:

procedure TMainForm.safelyLogMessage(mess : String);
begin
  protectlistMessages.Enter;
  try
    listMessages.Items.Add(mess);
    listMessages.ItemIndex := listMessages.Items.Count -1;
  finally
    protectListMessages.Leave;
  end;
end; 

Можете ли вы сказать мне, что лучше (быстрый + потокобезопасный) или показать мне третий способ писать сообщения в мой TListBox из моих потоков/процессов?


person Viktor Anastasov    schedule 03.07.2014    source источник
comment
Ни то, ни другое не хорошо. Вы всегда должны добавлять в список из основного потока. Поэтому вам нужно использовать Synchronize(), чтобы обеспечить правильный контекст потока.   -  person Disillusioned    schedule 03.07.2014


Ответы (1)


Ни один из ваших вариантов не годится. Вам нужно использовать вариант 3!

Дело в том, что весь доступ к элементам управления пользовательского интерфейса должен выполняться в основном потоке. Используйте TThread.Synchronize или TThread.Queue для маршалинга кода пользовательского интерфейса в основной поток пользовательского интерфейса. Как только вы это сделаете, код не будет нуждаться в дальнейшей сериализации, потому что сам процесс его запуска в потоке пользовательского интерфейса сериализует его.

Код может выглядеть так:

procedure TMainForm.Log(const Msg: string; const Color: TColor);
var
  Proc: TThreadProcedure;
begin
  Proc :=
    procedure
    begin
      ListBox1.AddItem(Msg, Pointer(Color));
      ListBox1.ItemIndex := ListBox1.Count-1;
    end;

  if GetCurrentThreadId = MainThreadID then
    Proc()
  else
    TThread.Queue(nil, Proc);
end;

В своем обновлении вы указываете, что вам нужно записать в список из другого процесса. Этого нельзя достичь ни с одним кодом в вопросе. Для этого вам нужна межпроцессная связь (IPC). Отправка сообщений Windows была бы разумным подходом, но есть и другие доступные варианты IPC. Но я думаю, что вы неправильно говорите, когда используете термин процесс. Я подозреваю, что вы не имеете в виду процесс, но что вы имеете в виду, я понятия не имею.

person David Heffernan    schedule 03.07.2014
comment
Не могли бы вы привести пример того, как должен выглядеть мой код в методах execute моих потоков? - person Viktor Anastasov; 03.07.2014
comment
@Roman_Bezjak На этот вопрос есть два ответа, которые могут помочь. Первый ответ доступен только в версиях Delphi, поддерживающих анонимные методы. Второй требуется, если вы застряли на более старой версии. - person Disillusioned; 03.07.2014
comment
Но если у меня есть таймер и его событие onTimer не имеет метода synchronize, что мне делать? - person Viktor Anastasov; 03.07.2014
comment
Таймер? В вопросе нет таймеров. В любом случае таймеры должны выполняться в основном потоке. Хорошо TTimer делает, потому что MakeObjectInstance не является потокобезопасным. Что еще важно здесь, что вы не говорите нам? - person David Heffernan; 03.07.2014
comment
У меня просто есть много потоков, которые пишут в этот TListBox под названием «listMessages» — два таймера, пара потоков, которые я создаю сам (их может быть 50 или больше) и событие OnExecute TCPServer, которое также пишет в listMessages — в основном я хочу (если это возможно) иметь один метод записи, который все они могут использовать для помещения информации в listMessages. - person Viktor Anastasov; 03.07.2014
comment
Код, который выполняется в потоке пользовательского интерфейса, как, я надеюсь, делают ваши методы таймера, не нуждается в вызове Synchronize или Queue, хотя это не повредит, если они это сделают. Код вне потока пользовательского интерфейса должен вызывать Synchronize или Queue. Вы уже поняли, что звонить Synchronize и Queue делать? А какую версию Delphi вы используете? - person David Heffernan; 03.07.2014
comment
Я изменил содержание вопроса - под потоками/процессами я подразумеваю объекты класса TThread, таймеры и событие TCPServer OnExecute. - person Viktor Anastasov; 03.07.2014
comment
Мне не кажется, что вы еще очень хорошо разбираетесь в этих вопросах. Не могли бы вы ответить на вопросы из моего предыдущего комментария? - person David Heffernan; 03.07.2014
comment
Я использую Делфи XE5. Итак, вы говорите, что в моих событиях OnTimer и OnExecute я могу просто ввести listMessages.Items.Add('some info'), и это будет потокобезопасно? - person Viktor Anastasov; 03.07.2014
comment
Вот поэтому и прошу помощи - так как не очень хорошо разбираюсь в этих вопросах :( - person Viktor Anastasov; 03.07.2014
comment
Я говорю, что код в потоке пользовательского интерфейса не нуждается в синхронизации, а код в других потоках должен использовать Synchronize или Queue. Знаете ли вы, выполняется ли какая-либо часть кода в основном потоке пользовательского интерфейса? - person David Heffernan; 03.07.2014
comment
Я думаю, что большая часть из них работает — да, но я не уверен насчет события OnExecute — оно выполняется в основном потоке пользовательского интерфейса? - person Viktor Anastasov; 03.07.2014
comment
Вам действительно нужно знать такую ​​информацию. В любом случае я добавил код, который, надеюсь, поможет. - person David Heffernan; 03.07.2014
comment
Спасибо, Дэвид. Я опубликую еще один вопрос, указав, что я хочу сделать, и покажу больше своего кода. - person Viktor Anastasov; 03.07.2014
comment
@Roman_Bezjak: Вам не нужно задавать новый вопрос. Дэвид только что дал вам ответ здесь, в ваших событиях execute/ontimer вы просто вызываете Logprocedure... - person whosrdaddy; 03.07.2014