Delphi TTask WaitForAll 与 Synchronise 的区别

4

我在线程方面的经验有限。我想要并行读取一些基本数据库表,需要等待所有表格都被读取完毕,然后程序才能合理地继续进行。在这方面,阻塞主线程对我来说是可以接受的。

这段代码(简化版)运行良好:

procedure ReadDBMultiThread;
  var ATasks : Array of ITask;
begin
  SetLength(ATasks, 3);
  ATasks[0] := TTaskCreate(procedure() begin DB_ReadTable1; end);
  ATasks[1] := TTaskCreate(procedure() begin DB_ReadTable2; end);
  ATasks[2] := TTaskCreate(procedure() begin DB_ReadTable3; end);

  ATasks[0].Start;
  ATasks[1].Start;
  ATasks[2].Start; 

  TTask.WaitForAll(ATasks);
end;

然而,假设我想更新主表单以显示进度,即已读取哪个数据库表(或执行任何其他必要的主线程工作)。显然,我不能使用Synchronize(),因为那会导致与WaitForAll()死锁;而我也不能使用Queue(),因为那会在WaitForAll()完成后执行。那么,有没有好的解决方案来解决“WaitForAll vs Synchronize”情况?我猜,这一定是许多人遇到的情况……需要等待所有操作都完成,但又想要更新主线程……我考虑了类似于下面的伪代码,替换WaitForAll()语句:
repeat 
  Applicaton.ProcessMessages; // or "ProcessSynchroniseMessages"
until "AllTaskCompleted"(ATasks);

这样做有用吗?有更好的解决方案吗?

我能自己编写像ProcessMessages一样的例程,但限制在同步消息上,即其他主窗体事件在稍后执行,可以吗?

非常感谢您的帮助!


1
@DavidHeffernan: "阻塞主线程对我来说没问题" - Remy Lebeau
@DavidHefferman:在等待绝对必要的数据时,您建议在主窗体中做什么?除了显示沙漏光标并等待数据到达之外,我想不出更多的做法... - HJay
1
@HJay:至少要保持对UI绘制的响应。 - Remy Lebeau
1
@DavidHeffernan:当然,您教授最佳实践是正确的。正如您所看到的,我明确地问过您在等待数据的情况下该如何解决。当然,我有兴趣做到正确。我相信,Remy Lebeau找到了一个非常好的方法来回答我的问题,甚至提供了比我要求的更好的解决方案。我希望您对这个解决方案也感到满意,因为它更新了GUI并等待我的数据出现。 - HJay
不,我认为这是一个薄弱的解决方案。但这就是你要求的。让任务传递一条消息来表明它们已完成是解决这个问题的方法。然后你就可以避免可怕的“处理消息反模式”。 - David Heffernan
显示剩余3条评论
1个回答

7

TThread.Synchronize()TThread.Queue()不使用窗口消息(虽然有一条消息用于“唤醒”主线程以发出挂起请求的信号,但实际的同步本身并非基于消息)。请求被放入全局队列中,在主线程处于空闲状态或检测到“唤醒”消息时,会检查该队列。您可以通过直接调用Classes.CheckSynchronize()函数手动泵送同一队列:

while not TTask.WaitForAll(ATasks, 1000) do
begin
  // process any pending TThread.Synchronize() and TThread.Queue() requests
  CheckSynchronize(0);
  // process any pending UI paint requests, but not other messages
  Application.MainForm.Update;
  // anything else you need...
end;

谢谢,这正是我所要求的...不过现在已经1点20分了,所以我要推迟评估到明天... - HJay
@Remy 当消息队列被清空时,才会合成WM_PAINT消息。因此,处理任何未完成的UI绘制请求,但不能处理其他消息 - David Heffernan
@DavidHeffernan:是的,它可以。你所说的对于从消息队列中检索到的WM_PAINT消息是正确的,但是有其他方式让窗口接收WM_PAINT消息。例如,TWinControl.Update()调用Win32 UpdateWindow()来强制立即发送一个绕过消息队列的WM_PAINT消息,如果控件的任何部分已被标记为无效等待绘制。 - Remy Lebeau
希望所有的绘画消息都能以这种方式到达。 - David Heffernan

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