我正在编写一些通过dll与外部硬件通信的软件(移动一些电机并读回一些值)。对dll的调用是阻塞的,可能不会以10秒的顺序返回。该软件通过移动硬件,读取并重复多个点来执行扫描。一次扫描可能需要30分钟才能完成。在扫描运行时,我显然希望GUI能够响应,并且在每个点都要更新输入数据的实时图形(在MDI子项中)。多线程似乎是解决这个问题的明显选择。
我的问题是,在扫描期间更新图表的最佳方法是什么?并回调主VCL线程?
我目前有一个TThread后代,它在ChildForm的public var部分执行'scan logic'和一系列双精度。我需要从线程中填写此数组,但我不知道是使用Synchronize或CriticalSection还是PostMessage或其他方法。每次添加新值时,主VCL线程都需要更新图形。我是否真的有一个中间对象用于全局var的数据,并以某种方式单独从Thread和ChildForm访问它?
答案 0 :(得分:6)
从线程更新GUI的最简单方法是将anonymous methods
与TThread.Synchronize
和TThread.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队列。数据流将如下:
代码看起来像这样:
队列......
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以避免在线程仍在运行(并写入内存)时释放数据结构。