2011-04-08 133 views
11

该应用程序使用Delphi XE编写。在线程间同步/发送数据

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

当老板创建一个TWorker实例时,它会为其分配一个调用同步的方法,当Worker完成它正在做的事情时,它调用这个方法,该方法允许Boss访问Worker上的记录。

但是我觉得这是一个问题,调用同步似乎锁定了整个应用程序 - 阻止主(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; 

然后在我的老板线程,我会做这样的事情

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; 
+1

哪个Delphi版本(自2009年以来有一些新的选择)? – mjn 2011-04-08 20:49:56

+0

Delphi XE - 谢谢 – Wizzard 2011-04-08 21:07:02

+1

我认为使用消息在线程之间传递数据,不需要“应用程序”同步机制(它在线程“框架”中),比在共享数据周围使用同步原语要优雅得多。 – Misha 2012-02-10 06:00:52

回答

9

Synchronize专门设计用于执行主线程中的代码;这就是为什么它似乎锁定了一切。

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

  • 添加回调到每个工作线程, 和它的创建时从老板线程 分配给它。它可以返回 作为参数,以及 线程标识或其他标识符。

  • 后使用 PostThreadMessage从工作线程 老板线程的消息。 的缺点是,老板 线程必须有一个窗口句柄 (请参阅 Delphi帮助和David Heffernan的下面的评论)中的Classes.AllocateHWnd

  • 使用高质量的第三方 线程库。见 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; 
+0

注意不是线程安全的Classes.AllocateHWnd。你需要连续调用AllocateHWnd和DeallocateHWnd。我不知道为什么VCL不这样做,并在我的代码钩我这些例程来实现线程安全。 – 2011-04-08 22:07:48

+0

@大卫:我知道。 (我应该特别提到它作为另一个缺点。感谢您的更正。 – 2011-04-08 22:29:44

+0

嗨,看来这是什么导致我的问题。然而,你的第一个解决方案是“添加一个回调到每个工作线程”这基本上是什么我已经完成了,所以它有什么不同? – Wizzard 2011-04-08 22:30:05

8

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

+0

TThreadedQueue在多线程环境中与许多消费者或许多生产者存在问题。改用更轻量级的队列。请参阅[链接](http://www.pascalgamedevelopment.com/showthread.php?4961-freepascal-Delphi-thread-safe-queue) – 2011-04-08 22:27:08

+0

我不知道这是如何回答这个问题的:“有没有办法在worker中调用Syncronize来等待Boss线程? - 你可以解释吗? – 2011-04-09 00:35:01

1

正如我在2009年和更高德尔福提到了新的选择,这里是线程间生产者/消费者通信的例子的链接,基于新的objct锁,在我的博客:

Thread Synchronization with Guarded Blocks in Delphi

note regarding the deprecated methods TThread.Suspend和 TThread.Resume,在Embarcadero DocWiki为Delphi 建议“线程 同步技术应该是 基于SyncObjs.TEvent和 SyncObjs.TMutex。“然而,自Delphi 2009:TMonitor以来,有另一个同步类 可用。 它使用对象锁定这已经 在此版本中引入了...类TWorker

+0

为什么TMutex首选临界区?对我来说似乎很奇怪。 – 2011-04-09 08:21:22

0

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

+1

Windows服务可以有Windows消息。所有Windows应用程序的核心都包含一个消息泵 – 2012-12-19 03:22:25

0

解决!(回答采取的问题)
修复这个问题,其中两倍。
首先删除TWorker SendBossResult方法中的syncronization调用。

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

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