从TThread发送数据到主VCL线程

3
我正在编写一个软件,通过dll与外部硬件通信(移动一些电机并返回一些值)。对dll的调用是阻塞的,可能需要10秒左右才能返回。该软件通过移动硬件、进行读数并重复执行若干次来执行扫描。每次扫描可能需要30分钟左右才能完成。在扫描过程中,我显然希望GUI具有响应性,并且在每个点处更新即时图形(在MDI子窗口中)。多线程似乎是这个问题的明显选择。
我的问题是,最佳的线程方式和如何回话到主VCL线程以在扫描期间更新图形?
我目前有一个单个TThread派生类,它执行“扫描逻辑”和ChildForm中公共var部分的双精度数组。我需要从线程中填充此数组,但我不知道是使用Synchronize还是使用CriticalSection或PostMessage或其他某种方法。每当添加新值时,主VCL线程都需要更新图形。我真的应该为数据创建一个全局var的中介对象,并从线程和ChildForm分别访问吗?

1
很多选项。我们怎么能说哪个是最好的呢?Synchronize会起作用,并且是最简单的选择。让主线程拥有数据,并让工作线程使用Synchronize发送新数据以供主线程追加和显示。 - David Heffernan
我想实现的是一个可扩展的解决方案,也就是当我的扫描可以多次执行,并且每个扫描都具有额外的属性(颜色、标题、点数等),这时逻辑应该放在哪里?应该将逻辑放在线程内部还是让线程尽可能简单地执行单个测量呢? - Steve Magness
你所有的需求都会在评论中详细说明吗? - David Heffernan
抱歉,我觉得自己还有点不清楚“真正”的问题是什么——这是一个更一般性的“架构”问题,而不是一个简单的问题。我重写了这篇文章几次,试图弄清楚我需要问什么,但是我必须想办法开始动起来… - Steve Magness
你可以使用匿名函数与 TThread.SynchronizeTThread.Queue,使代码更易于维护和阅读。我真的不喜欢 Ghigo 建议的非常低级的方法。 - David Heffernan
哪一个版本的 Delphi? - GolezTrol
4个回答

6

从线程更新GUI的最简单方法是使用匿名方法TThread.SynchronizeTThread.Queue结合使用。

procedure TMyThread.Execute;
begin
  ...
  Synchronize(  // Synchronous example
    procedure
    begin
      // Your code executed in main thread here 
    end
  );
  ...
  Queue( // Asynchronous example
    procedure
    begin
      // Your code executed in main thread here
    end
  );
end;

经常需要异步传递值,这通常需要 "捕获" 值。
procedure TMyThread.PassAValue(anInteger: Integer);
begin
  Queue(
    procedure
    begin
      // Use anInteger in main thread 
    end
  );
end;

procedure TMyThread.Execute;
var
  myInt: Integer;
begin
  ...
  PassAValue(myInt);  // Capture myInt
  ...
end;

当匿名方法使用变量时,对该变量的引用会被捕获。这意味着如果在执行匿名方法之前更改了变量的值,则会使用新值。因此需要捕获“值”。可以在synchronize-and-queue-with-parameters中找到更详细的示例,由@UweRaabe提供。

1

我发现从后台线程填充TThreadList,然后向主线程发布消息,通知列表中有新项目,最后在主线程中处理列表是简单易于维护的。

使用这种方法,您可以将尽可能多的读数存储在列表中,每次主线程接收到消息时,它只需一次性处理列表中的所有项目。

为读数定义一个类,实例化它们,并将它们添加到后台线程的列表中。当您弹出列表时,请不要忘记在主线程中释放它们。


1
如果您想投资更多,那么可以在简单的同步调用之外添加一个简单的FIFO队列和消息传递。数据流程如下:
1.线程将数据放入队列中。
2.线程向主线程窗口发布一条消息。我不关心哪个窗口 :)
3.处理可用数据的消息,并根据需要处理队列中的任何消息。
代码大致如下:
队列...
const
  WM_DataAvailable = WM_USER + 1;

var
  ThreadSafeQueue: TThreadSafeQueue;

数据被放入队列中...
procedure PutDataIntoQueue;
var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  ThreadSafeQueue.Enqueue(MyObject);
  PostMessage(FMainWindowHandle, WM_DataAvailable, 0, 0);
end;

并且处理中...

procedure ProcessDataInTheQueue(var Msg: TMessage); message WM_DataAvailable;

procedure ProcessDataInTheQueue(var Msg: TMessage);
var
  AnyValue: TAnyValue;
  MyObject: TMyObject;
begin
  while ThreadSafeQueue.Dequeue(AnyValue) do
  begin
    MyObject := TMyObject(AnyValue.AsObject);
    try
      // process the actual object as needed
    finally
      MyObject.Free
    end;
  end;
end;

代码是不使用Delphi和检查编写的,因此可能包含错误。我使用我的免费线程安全队列和TAnyValue示例进行了演示。您可以在此处找到它们:

http://www.cromis.net/blog/downloads/

此外,请注意我没有检查PostMessage是否实际发送。您应该在生产代码中进行检查。

0

在你的线程内使用postmessage并向主窗体句柄发送消息。 注册一个(或多个)自定义消息,并编写处理程序。

const WM_MEASURE_MESSAGE = WM_USER + 1;

创建一个线程类,添加一个MainFormHandle属性(Thandle或cardinal)。 创建暂停的线程,使用主窗体句柄设置MainFormHandle,然后恢复线程。 当您有一个新的测量时,将data1和data2 dword分配为来自测量的一些数据,然后。
PostMessage(fMainFormHandle,WM_MEASURE_MESSAGE,data1,data2);

在主窗体中,您有消息处理程序:
procedure MeasureMessage(var msg: TMessage); message WM_MEASURE_MESSAGE;
begin
  // update graph here
  // msg.wparam is data1
  // msg.lparam is data2
end;

如果您需要从线程向主窗体发送更多的数据,可以在主上下文中创建一个适当的结构来存储所有的测量数据,将引用传递给线程,让线程写入数据,并使用消息告诉主窗体新数据的位置(例如数组索引)。在主上下文中使用TThread.Waitfor来避免在线程仍在运行(并写入内存)时释放数据结构。

不需要创建挂起线程。将窗口句柄传递给线程的构造函数即可。但是,您需要确保主窗体不会重新创建其窗口句柄。如果我在做这个,我个人会使用AllocateHwnd。但我甚至不会这样做。我会使用TThread.Queue,避免重新实现产品中已经存在的功能。 - David Heffernan
我不喜欢TThread.Synchronize的方法,因为它会在执行主上下文代码时停止运行线程。我也不喜欢TThread.Queue的方法,因为当线程完成时,您会失去所有未处理的调用,因为线程队列在线程完成时被清空。此外,TThread.Queue不是线程安全的,所以您必须小心。 - Ghigo
你将会发送什么到data1和data2?如果这些是指针,你必须确保它们仍然有效。这意味着线程将不得不创建它们,处理消息的主窗体需要清理它们。我认为这并不是一个非常好的方法。 - GolezTrol
data1和data2不是指针,而是双字。如果您需要在图表上更新X和Y坐标,则它们已足够。 - Ghigo
@David 我深入研究了TThread.Queue,我发现这个链接:link。使用TThread.Queue时,如果线程更新变量,则必须注意。如果线程在执行Queue方法之前更新变量,则会丢失数据。如果您需要更新进度条,则TThread.Queue是100%可靠的。在更复杂的设计中,我不会信任TThread.Queue。这只是我的意见,我可能(很容易)错了。 - Ghigo
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接