在线程之间同步/发送数据

时间:2011-04-08 20:38:31

标签: multithreading delphi synchronization delphi-xe

该应用程序是用Delphi XE编写的。

我有两个类,TBoss和TWorker,它们都是基于TThread。 TBoss是一个单实例线程,启动后会创建大约20个TWorker线程。

当老板创建TWorker实例时,它会为其分配一个调用同步的方法,当Worker完成正在执行的操作时,调用此方法允许Boss访问Worker上的记录。

但是我觉得这是一个问题,调用synchronize似乎是锁定整个应用程序 - 阻塞主(ui)线程。真的应该只是将该工作者与老板线程同步......

以前我使用消息/打包记录在线程之间发送内容,这些内容运行良好。然而,这样做更清洁,更好......只是非常阻塞。

有没有办法在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的方法 - 这就是在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;

5 个答案:

答案 0 :(得分:10)

Synchronize专门用于执行主题中的代码;这就是它似乎锁定一切的原因。

您可以使用多种方式从工作线程到老板线程进行通信:

  • 为每个工作线程添加一个回调, 并从老板线程中分配它 当它被创建时。它可以传回来 无论作为参数,还是a 线程ID或其他一些标识符。

  • 从工作线程发布消息 给老板线程使用 PostThreadMessage。该 这里的劣势就是老板 线程必须有一个窗口句柄 (见Classes.AllocateHWnd) 德尔福帮助和David Heffernan的评论如下)。

  • 使用优质的第三方 线程库。看到 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;

答案 1 :(得分:8)

您可以使用线程安全队列。在DelphiXE中有TThreadedQueue。如果您没有DXE,请尝试使用OmniThreadLibray - 这个库非常适合所有线程问题。

答案 2 :(得分:1)

正如我在Delphi 2009及更高版本中提到的新选项,这里有一个指向线程之间生产者/消费者通信的示例的链接,基于新的objct锁,在我的博客中:

Thread Synchronization with Guarded Blocks in Delphi

  

note regarding the deprecated methods TThread.Suspend和   TThread.Resume,德尔福的Embarcadero DocWiki   建议“线程   同步技术应该是   基于SyncObjs.TEvent和   SyncObjs.TMutex。“但是,   另一个同步类   自Delphi 2009以来可用:TMonitor。   它使用已经存在的对象锁   在这个版本中介绍......

答案 3 :(得分:0)

public类的

TWorker属性必须具有getset方法,因此您可以使用Tcriticalsection来提供属性的值。否则,您将遇到线程安全问题。您的示例似乎没问题,但在现实世界中,数千个线程访问相同的值会导致读取错误。使用关键部分..你不必使用任何同步。这样您就可以避免转到窗口的消息队列并提高性能。此外,如果您在Windows服务应用程序中使用此代码(不允许使用Windows消息),此示例将不起作用。除非可以访问Windows消息队列,否则synchronize方法不起作用。

答案 4 :(得分:0)

解决!! (从问题中回答)
这个问题的解决方案是两次折叠 首先删除TWorker SendBossResult方法中的同步调用。

第二步将一个fProcessWorkerResult CritialSection添加到TBoss类。在TBoss的创建/销毁中创建并释放它。在ProcessWorkerResults方法中,调用fProcessWorkerResult.Enter和fProcessWorkerResult.leave围绕代码,这些代码需要安全地从多个工作流结果流入。

以上是Kens代码和后续评论后的结论。非常感谢亲切的先生,给你帽子!。