Синхронизация/отправка данных между потоками
Приложение написано в Delphi XE.
У меня есть два класса: TBoss и TWorker, которые основаны на TThread.
TBoss - это поток одного экземпляра, который запускается, а затем создает около 20 потоков TWorker.
Когда босс создает экземпляр TWorker, он назначает ему метод вызова синхронизации, когда рабочий закончил с тем, что он делает, и вызывает этот метод, который позволяет Боссу получить доступ к записи на Рабочем месте.
Однако я чувствую, что это проблема, вызывающая синхронизация, похоже, блокирует все приложение - блокирует основной (ui) поток. На самом деле нужно просто синхронизировать этого работника с потоком босса....
Раньше я использовал сообщения/упакованные записи для отправки контента между потоками, которые хорошо работали. Однако делать это таким образом намного чище и красивее.... просто очень блокирует.
Есть ли способ вызвать Syncronize в рабочем состоянии, чтобы ждать только поток Boss?
Мой код:
type
TWorker = class(TThread)
private
fResult : TResultRecord;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
....
end;
...
...
procedure TWorker.SendBossResults;
begin
if (Terminated = False) then
begin
Synchronize(SendResult);
end;
end;
procedure TWorker.SendResult;
begin
if (Terminated = false) and Assigned(FOnSendResult) then
begin
FOnSendResult(Self);
end;
end;
Тогда в моей теме Boss я сделаю что-то вроде этого
var
Worker : TWorker;
begin
Worker := TWorker.Create;
Worker.OnTerminate := OnWorkerThreadTerminate;
Worker.OnSendResult := ProcessWorkerResults;
Итак, у моего босса есть метод ProcessWorkerResults - это то, что запускается на Synchronize (SendResult); рабочего.
procedure TBoss.ProcessWorkerResults(Sender: TObject);
begin
if terminated = false then
begin
If TWorker(Sender).Result.HasRecord then
begin
fResults.Add(TWorker(Sender).Result.Items);
end;
end;
end;
Ответы
Ответ 1
Synchronize
специально разработан для выполнения кода в потоке main; почему он, кажется, блокирует все.
Вы можете использовать несколько способов связи из рабочих потоков с потоком босса:
-
Добавить обратный вызов для каждого рабочего потока,
и назначить его из потока босса
когда он создан. Он может вернуться назад
как параметры, наряду с
идентификатор потока или другой идентификатор.
-
Опубликовать сообщение из рабочего потока
к потоку босса, используя
PostThreadMessage.
Недостатком является то, что босс
поток должен иметь ручку окна
(см. Classes.AllocateHWnd в
Справка Delphi и комментарий Дэвида Хеффернана ниже).
-
Используйте хорошее качество сторонних
потоковая библиотека. Видеть
OmniThreadLibrary - бесплатно,
ОС и очень хорошо написаны.
Мой выбор был бы третьим. Примоз сделал для вас всю тяжелую работу.:)
После вашего комментария, здесь что-то вроде первого предложения. Обратите внимание, что это непроверено, так как написав код для потока TBoss и TWorker + тестовое приложение немного длиннее, когда я прав в эту минуту... Этого должно быть достаточно, чтобы дать вам я надеюсь.
type
TWorker = class(TThread)
private
fResult : TResultRecord;
fListIndex: Integer;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
property ListIndex: Integer read FListIndex write FListIndex;
....
end;
type
TBoss=class(TThread)
private
FWorkerList: TThreadList; // Create in TBoss.Create, free in TBoss.Free
...
end;
procedure TWorker.SendBossResults;
begin
if not Terminated then
SendResult;
end;
procedure TBoss.ProcessWorkerResults(Sender: TObject);
var
i: Integer;
begin
if not terminated then
begin
If TWorker(Sender).Result.HasRecord then
begin
FWorkerList.LockList;
try
i := TWorker(Sender).ListIndex;
// Update the appropriate record in the WorkerList
TResultRecord(FWorkerList[i]).Whatever...
finally
FWorkerList.UnlockList;
end;
end;
end;
end;
Ответ 2
Вы можете использовать поточную безопасную очередь. В DelphiXE есть TThreadedQueue. Если у вас нет DXE, попробуйте OmniThreadLibray - эта библиотека очень хороша для всех проблем с потоками.
Ответ 3
Как я уже упоминал о новых опциях в Delphi 2009 и выше, здесь приведена ссылка на пример для связи между производителями и потребителями между потоками на основе новых блокировок objct в моем блоге:
Синхронизация потоков с защищенными блоками в Delphi
В примечании относительно устаревших методов TThread.Suspend и TThread.Resume, DocWiki для Embarcadero для Delphi рекомендует, чтобы "поток методы синхронизации должны быть на основе SyncObjs.TEvent и SyncObjs.TMutex". Однако есть, другой класс синхронизации доступный с Delphi 2009: TMonitor. Он использует блокировку объекта, которая была представленный в этой версии...
Ответ 4
public
свойства класса TWorker
ДОЛЖНЫ иметь методы get
и set
, поэтому вы можете использовать Tcriticalsection
для получения значений свойств. В противном случае у вас будут проблемы с потоками. Ваш пример выглядит нормально, но в реальном мире тысячи потоков, обращающихся к одному и тому же значению, приведут к ошибке чтения. Используйте критические разделы.. и вам не нужно будет использовать синхронизацию. Таким образом, вы избегаете перехода в очереди сообщений окон и повышения производительности. Кроме того, если вы используете этот код в приложении для Windows-приложений (там, где не разрешены сообщения Windows), этот пример не будет работать. Метод синхронизации не работает, если нет доступа к очереди сообщений Windows.
Ответ 5
Решено!! (ответ от вопроса)
Исправления сделаны для этой проблемы, где два раза.
Сначала удалите вызов syncronization в методе TWorker SendBossResult.
Второе добавьте класс fProcessWorkerResult CritialSection в TBoss. Создайте и освободите это для создания/уничтожения TBoss. В методе ProcessWorkerResults вызывается метод fProcessWorkerResult.Enter и fProcessWorkerResult.leave вокруг кода, который должен быть безопасным из нескольких потоков рабочих результатов.
Выше было заключение после кода Кенса и последующий комментарий. Большое спасибо, сэр, шлемы для вас!