【发布时间】:2014-04-29 08:34:59
【问题描述】:
我发现如果以TThread.Queue 排队的方法调用调用TApplication.WndProc 的方法(例如ShowMessage),则允许在原始方法完成之前运行后续排队的方法。更糟糕的是,它们似乎没有按 FIFO 顺序调用。
[编辑:实际上它们确实以 FIFO 顺序开始。使用ShowMessage,看起来后面的会先运行,因为在对话框出现之前调用了CheckSynchronize。这会将下一个方法排入队列并运行它,直到后一个方法完成后才返回。只有这样才会出现对话框。]
我试图确保从工作线程排队以在 VCL 线程中运行的所有方法都以严格的 FIFO 顺序运行,并且它们都在工作线程被销毁之前完成。
我的另一个限制是我试图保持 GUI 与业务逻辑的严格分离。在这种情况下,线程是业务逻辑层的一部分,因此我不能使用来自OnTerminate 处理程序的PostMessage 来安排销毁线程(正如其他地方的许多贡献者所建议的那样)。所以我在 TThread.Execute 退出之前在最终的排队方法中设置了FreeOnTerminate := True。 (因此需要它们以严格的 FIFO 顺序执行。)
这就是我的 TThread.Execute 方法的结束方式:
finally
// Queue a final method to execute in the main thread that will set an event
// allowing this thread to exit. This ensures that this thread can't exit
// until all of the queued procedures have run.
Queue(
procedure
begin
if Assigned(fOnComplete) then
begin
fOnComplete(Self);
// Handler sets fWorker.FreeOnTerminate := True and fWorker := nil
end;
SetEvent(fCanExit);
end);
WaitForSingleObject(fCanExit, INFINITE);
end;
但正如我所说,这不起作用,因为这个排队的方法在一些早期的排队方法之前执行。
任何人都可以提出一种简单而干净的方法来完成这项工作,或者一个简单而干净的替代方案吗?
[到目前为止,我想出的唯一一个保持关注点分离和模块化的想法是给我的TThread 子类一个自己的WndProc。然后我可以直接使用PostMessage 这个 WndProc 而不是主窗体。但我希望有更轻量级的东西。]
感谢到目前为止的答案和 cmets。我现在明白,我上面的带有排队的SetEvent 和WaitForSingleObject 的代码在功能上等同于在最后调用Synchronize 而不是Queue,因为Queue 和Synchronize 共享同一个队列。我首先尝试了Synchronize,但它失败的原因与上面的代码失败的原因相同——早期的排队方法调用消息处理,因此最终的Synchronize 方法在早期的排队方法完成之前运行。
所以我仍然坚持原来的问题,现在归结为:我可以干净地确保所有排队的方法都在工作线程被释放之前完成,我是否可以在不使用 @ 的情况下干净地释放工作线程987654342@,需要一个窗口句柄才能发布到(我的业务层无权访问)。
我还更新了标题以更好地反映原始问题,尽管如果合适的话,我很乐意提供不使用TThread.Queue 的替代解决方案。如果有人能想出更好的标题,请编辑它。
另一个更新:David Heffernan 的This answer 建议在一般情况下使用PostMessage 和特殊的AllocateHWnd,如果TThread.Queue 不可用或不合适。值得注意的是,在主窗体中使用PostMessage 是绝对不安全的,因为窗口可以通过更改其句柄而自发地重新创建,这将导致旧句柄的所有后续消息丢失。这为我采用这个特定的解决方案提供了一个强有力的论据,因为在我的情况下创建隐藏窗口没有额外的开销,因为使用 PostMessage 的 any 应用程序应该这样做 - 即我的关注点分离论点是无关紧要。
【问题讨论】:
-
似乎队列在这里不是正确的选择。您可以考虑改用 Synchronize。
-
@UweRaabe 我应该说排队的方法需要与工作线程异步执行。工作线程只有在完成所有工作并准备退出时才能等待。
-
恕我直言,
all of the queued methods have completed before the worker thread is freed表示此线程与在不同线程的上下文中执行的排队方法之间存在(太)强耦合 -
@mjn 出现这种情况是因为要求主线程可以随时终止工作线程。但是由于主线程不能使用通常的 PostMessage 技术将工作线程从其 OnTerminate 处理程序中释放出来,所以我们必须使用 FreeOnTerminate := True,但是直到所有排队的方法都完成后才能设置,因为所有方法仍在队列中当线程被销毁时,线程会被销毁。但我对实现目标的其他方式持开放态度。欢迎所有建议。
-
@mjm 实际上,如果线程有自己的 WndProc,我可以使用 PostMessage 来释放线程;可以说,无论如何,这是唯一可靠的方法。但是,如果我为此使用 Windows 消息传递,那么我还不如全力以赴并使用 Windows 消息传递而不是到处使用 TThread.Queue。说到这里,我还不如使用本机 Win32 API 而不是 TThread。但我正在寻找最简单的可行解决方案,不利用 VCL 似乎很愚蠢。
标签: multithreading delphi