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

发布于 2024-10-31 00:19:21 字数 1770 浏览 1 评论 0原文

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

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

当 Boss 创建 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 的方法 - 这就是在 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;

The app is written in Delphi XE.

I have two classes, a TBoss and TWorker, which are both based of of TThread.
The TBoss is a single instance thread, which starts up and then will create about 20 TWorker threads.

When the boss creates a instance of TWorker it assigns it a method to call synchronize on, when the Worker has finished with what it's doing it calls this method which allows the Boss to access a record on the Worker.

However I feel this is a problem, calling synchronize appears to be locking up the whole application - blocking the main (ui) thread. Really it should just be synchronizing that worker to the boss thread....

Previously I used messages/packed records to send content between threads which worked well. However doing it this way is much cleaner and nicer.... just very blocking.

Is there a way to call Syncronize in the worker to only wait for the Boss thread?

My code:

    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;

Then in my Boss thread I will do something like this

    var 
      Worker  : TWorker; 
    begin 
      Worker              := TWorker.Create; 
      Worker.OnTerminate  := OnWorkerThreadTerminate; 
      Worker.OnSendResult := ProcessWorkerResults;

So my boss then has a method called ProcessWorkerResults - this is what gets run on the Synchronize(SendResult); of the worker.

    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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

瞎闹 2024-11-07 00:19:21

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

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

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

  • 从工作线程发布消息
    到老板线程使用
    PostThreadMessage。这
    这里的缺点是老板
    线程必须有一个窗口句柄
    (请参阅 Classes.AllocateHWnd 中的
    Delphi 帮助和 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;

Synchronize is specifically designed to execute code in the main thread; that's why it seems to lock everything up.

You can use several ways to communicate from the worker threads to the boss thread:

  • Add a callback to each worker thread,
    and assign it from the boss thread
    when it's created. It can pass back
    whatever as parameters, along with a
    thread ID or some other identifier.

  • Post a message from the worker thread
    to the boss thread using
    PostThreadMessage. The
    disadvantage here is that the boss
    thread has to have a window handle
    (see Classes.AllocateHWnd in the
    Delphi help and David Heffernan's comment below).

  • Use a good quality third-party
    threading library. See
    OmniThreadLibrary - it's free,
    OS, and extremely well written.

My choice would be the third. Primoz has done all the hard work for you. :)

After your comment, here's something along the lines of my first suggestion. Note that this is untested, since writing the code for a TBoss and TWorker thread + a test app is a little long for the time I have right this minute... It should be enough to give you the gist, I hope.

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;
許願樹丅啲祈禱 2024-11-07 00:19:21

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

You could use a thread safe queue. In DelphiXE there is the TThreadedQueue. If you don't have DXE, try OmniThreadLibray - this library is very good for all threading issues.

他夏了夏天 2024-11-07 00:19:21

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

Delphi 中与受保护块的线程同步

有关已弃用方法的说明中,TThread.Suspend 和
TThread.Resume,Delphi 的 Embarcadero DocWiki
建议“线程
同步技术应该是
基于SyncObjs.TEvent和
SyncObjs.TMutex。“但是,
另一个同步类
自 Delphi 2009 年起可用:TMonitor。
它使用已经被锁定的对象锁
此版本中引入...

As I mentioned new options in Delphi 2009 and higher, here is a link to an example for Producer / Consumer communication between threads, based on the new objct locks, in my blog:

Thread Synchronization with Guarded Blocks in Delphi

In a note regarding the deprecated methods TThread.Suspend and
TThread.Resume, The Embarcadero DocWiki for Delphi
recommends that “thread
synchronization techniques should be
based on SyncObjs.TEvent and
SyncObjs.TMutex.“ There is, however,
another synchronization class
available since Delphi 2009: TMonitor.
It uses the object lock which has been
introduced in this version ...

别念他 2024-11-07 00:19:21

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

public properties of the TWorker class MUST have get and set methods, so you can use a Tcriticalsection to give the values of the properties. Otherwise, you´d be having thread-safe issues. Your example seems ok, but in the real world, with thousands of threads accessing to the same value would result in an read error. Use critical sections.. and you wouldn´t have to use any Synchronize. This way you avoid going to the message queues of windows and improve performance. Besides, if you use this code in a windows service app, (where windows messages aren´t allowed), this example wouldn´t work. The synchronize method doesn´t work unless there´s access to the windows message queue.

予囚 2024-11-07 00:19:21

已解决!!(答案取自问题)
针对此问题所做的修复有两个部分。
首先删除 TWorker SendBossResult 方法中的同步调用。

其次,向 TBoss 类添加一个 fProcessWorkerResult CritialSection。在 TBoss 的创建/销毁中创建并释放它。在 ProcessWorkerResults 方法中,调用 fProcessWorkerResult.Enter 和 fProcessWorkerResult.leave 围绕需要避免多个工作结果流入的代码。

以上是 Kens 代码和后续评论之后的结论。非常感谢善良的先生,向您致敬!

Solved!! (answer taken from the question)
The fixes made for this problem where two fold.
First remove the syncronization call in the TWorker SendBossResult method.

Second add a fProcessWorkerResult CritialSection to TBoss class. Create and Free this in create/destroy of the TBoss. In the ProcessWorkerResults method call fProcessWorkerResult.Enter and fProcessWorkerResult.leave around the code which needs to be safe from multiple worker results streaming in.

The above was the conclusion after Kens code and follow up comment. Many thanks kind sir, hats off to you!.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文