将数据从TThread发送到主VCL线程

时间:2013-10-01 10:54:11

标签: multithreading delphi

我正在编写一些通过dll与外部硬件通信的软件(移动一些电机并读回一些值)。对dll的调用是阻塞的,可能不会以10秒的顺序返回。该软件通过移动硬件,读取并重复多个点来执行扫描。一次扫描可能需要30分钟才能完成。在扫描运行时,我显然希望GUI能够响应,并且在每个点都要更新输入数据的实时图形(在MDI子项中)。多线程似乎是解决这个问题的明显选择。

我的问题是,在扫描期间更新图表的最佳方法是什么?并回调主VCL线程?

我目前有一个TThread后代,它在ChildForm的public var部分执行'scan logic'和一系列双精度。我需要从线程中填写此数组,但我不知道是使用Synchronize或CriticalSection还是PostMessage或其他方法。每次添加新值时,主VCL线程都需要更新图形。我是否真的有一个中间对象用于全局var的数据,并以某种方式单独从Thread和ChildForm访问它?

4 个答案:

答案 0 :(得分:6)

从线程更新GUI的最简单方法是将anonymous methodsTThread.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 :(得分:1)

如果你想再投资一点简单的同步调用,它会阻塞主线程,你可以在它上面添加一个简单的FIFO队列。数据流将如下:

  1. 线程将数据放入队列。
  2. 线程将消息发布到主线程窗口。哪一个我不在乎:)。
  3. 您可以处理数据可用的消息,并根据需要处理队列中的所有消息。
  4. 代码看起来像这样:

    队列......

    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实际发送,我没有做任何检查。您应该在生产代码中检查它。

答案 2 :(得分:1)

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

使用此方法,您可以在列表中存储任意数量的读数,并且每次主线程收到消息时,它只会立即处理列表中的所有项目。

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

答案 3 :(得分:0)

在你的线程中使用postmessage并将消息发送到主表单句柄。 注册一个(或多个)自定义消息并为它们编写处理程序。

const WM_MEASURE_MESSAGE = WM_USER + 1;

创建一个线程类,添加一个MainFormHandle属性(Thandle或cardinal)。 创建线程暂停,使用主窗体句柄设置MainFormHandle,然后恢复线程。 如果有新度量,请使用measure中的某些数据分配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;

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