Синхронизация / отправка данных между потоками - PullRequest
12 голосов
/ 09 апреля 2011

Приложение написано на Delphi XE.

У меня есть два класса, TBoss и TWorker, которые оба основаны на TThread.TBoss - это отдельный экземплярный поток, который запускается, а затем создает около 20 потоков TWorker.

Когда босс создает экземпляр TWorker, он назначает ему метод для вызова синхронизации, когда Worker завершает работу сто, что он делает, вызывает этот метод, который позволяет Боссу получить доступ к записи на Рабочем.

Однако я чувствую, что это проблема, вызов синхронизации синхронизирует все приложение - блокирование основного (пользовательского интерфейса)нить.На самом деле он должен просто синхронизировать этого работника с потоком босса ....

Ранее я использовал сообщения / упакованные записи для отправки контента между потоками, который работал хорошо.Однако делать это таким способом намного чище и приятнее ... просто очень блокирует.

Есть ли способ вызвать 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 - это то, что получаетзапустить на Синхронизировать (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;

Ответы [ 5 ]

10 голосов
/ 09 апреля 2011

Synchronize специально разработан для выполнения кода в потоке main ;вот почему кажется, что все заблокировано.

Вы можете использовать несколько способов связи из рабочих потоков с потоком босса:

  • Добавить обратный вызов для каждого рабочего потокаи назначьте его из босса, когда он будет создан.Он может передавать все что угодно в качестве параметров вместе с идентификатором потока или другим идентификатором.

  • Публикация сообщения из рабочего потока в потоке босса с использованием PostThreadMessage .Недостатком здесь является то, что поток босса должен иметь дескриптор окна (см. Classes.AllocateHWnd в справке Delphi и комментарии Дэвида Хеффернана ниже).

  • ИспользованиеХорошая качественная сторонняя библиотека потоков.См. OmniThreadLibrary - это бесплатно, ОС и очень хорошо написано.

Мой выбор будет третьим. Primoz сделал всю тяжелую работу за вас.:)

После вашего комментария вот что-то вроде моего первого предложения.Обратите внимание, что это не проверено , так как написание кода для потока 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;
8 голосов
/ 09 апреля 2011

Вы можете использовать потокобезопасную очередь.В DelphiXE есть TThreadedQueue.Если у вас нет DXE, попробуйте OmniThreadLibray - эта библиотека очень хороша для всех проблем с многопоточностью.

1 голос
/ 09 апреля 2011

Как я уже упоминал о новых опциях в Delphi 2009 и более поздних версиях, в моем блоге приведена ссылка на пример связи между производителями и потребителями между потоками на основе новых блокировок объекта:

Синхронизация потоков с защищенными блоками в Delphi

В примечании об устаревших методах TThread.Suspend и TThread.Resume, Embarcadero DocWiki для Delphi рекомендует что-нить методы синхронизации должны быть на основе SyncObjs.TEvent и SyncObjs.TMutex. «Однако есть другой класс синхронизации доступно с Delphi 2009: TMonitor. Он использует блокировку объекта, которая была введено в этой версии ...

0 голосов
/ 02 января 2015

Решено !! (ответ взят из вопроса)
Исправления, исправленные для этой проблемы, где два раза.
Сначала удалите вызов синхронизации в методе TWorker SendBossResult.

Во-вторых, добавьте fProcessWorkerResult CritialSection в класс TBoss.Создайте и освободите это при создании / уничтожении TBoss.В методе ProcessWorkerResults вызовите fProcessWorkerResult.Enter и fProcessWorkerResult.leave вокруг кода, который должен быть защищен от потоковой передачи результатов нескольких рабочих.

Выше было заключение после кода Kens и последующего комментария.Большое спасибо, сэр, снимаю шляпу перед вами!

0 голосов
/ 10 февраля 2012

public свойства класса TWorker ДОЛЖНЫ иметь методы get и set, поэтому вы можете использовать Tcriticalsection для задания значений свойств. В противном случае у вас возникнут проблемы с безопасностью потоков. Ваш пример выглядит нормально, но в реальном мире, когда тысячи потоков обращаются к одному и тому же значению, это приведет к ошибке чтения. Используйте критические разделы .. и вам не придется использовать синхронизацию. Таким образом вы избегаете попадания в очереди сообщений окон и повышаете производительность. Кроме того, если вы используете этот код в приложении службы Windows (где сообщения Windows не разрешены), этот пример не будет работать. Метод синхронизации не работает, если нет доступа к очереди сообщений Windows.

...