我回来了另一个关于线程和同步的问题。想象一下,服务器应用程序必须执行冗长的操作,并且客户端希望他的GUI在等待服务器的响应时保持响应。我想到了以下模式:
TMonitor.Enter (FTCPClient);
try
WorkerThread := TWorkerThread.Create (SomeLengthyServerOperation);
while (not WorkerThread.Ready) do
Application.ProcessMessages;
DoSometingWithResults (WorkerThread.Result);
WorkerThread.Free;
finally
TMonitor.Exit (FTCPClient);
end;
WorkerThread是一个派生自TThread的简单类,它执行传递给其构造函数的函数,然后终止(使用Ready = True,结果为Result)。只要单击一个按钮,就会执行显示的代码。
现在我的问题:如果我非常快地点击按钮两次,我会得到一些奇怪的错误,看起来很像服务器和客户端之间的通信以某种方式被误解,我想通过锁定FTCPClient对象来避免。 Application.ProcessMessages执行后的事件处理程序是什么线程?是每个线程的TMonitor锁吗?这是否意味着如果我使用Application.ProcessMessages,那么锁不起作用?
目前我无法更好地解释。我希望有人明白我的观点。如果没有,请随时提问。
编辑:对于禁用和启用按钮:我对客户端代码一无所知。可能是一个按钮事件处理程序,可能是其他东西。基本上我想隐藏客户端代码的锁定。
答案 0 :(得分:7)
TMonitor仅阻止不同的线程获取锁定。发生的事情是:通过处理来自锁内的消息,您将返回到同一线程中的相同函数,这导致递归获取锁。然后,您的代码将创建一个新的工作线程,并开始全面循环。您可以禁用该按钮,以便在工作线程完成之前不能再次单击它。确保在开始处理消息之前禁用按钮,然后使用另一个try..finally块以确保重新启用它。根据其余代码的排列方式,您可能甚至不需要锁定。
答案 1 :(得分:3)
一些意见:
您的WorkerThread听起来就像您刚刚重新实现AsyncCalls。使用经过试验和测试的实现可能比编写自己的实现更好(除非您为了学习效果或因为您有非常特殊的要求而这样做)。请查看AsyncCalls和OmniThreadLibrary。
锁定应尽可能短,因此将整个反应包含在Monitor.Enter()和Monitor.Exit()中的按钮单击似乎是错误的。它服务的目的也是不可理解的。
在锁定时调用Application.ProcessMessages()可能会引入各种令人讨厌的意外。如果您需要保持代码不被重新输入,通常最好禁用所有UI元素作为OnClick处理程序的第一件事,并在处理程序完成时重新启用它们。还要注意,可以从同一个线程多次输入锁,它们只能实现多个线程的独占访问。
所有VCL都在主GUI线程中执行,因此只有在从后台线程调用相同代码时才需要锁定。
如果您查看此代码,您将看到通过在GUI线程中完成工作而不是生成工作线程,您可以实现相同的目标。
我已经在StackOverflow上多次发布此链接,但请注意遵循this list posting中的建议,以便在进行多线程编程时记住一些事项。关于第五点,它有一些很好的建议。
编辑:很难确切地说,但您的代码应该是这样的:
procedure TForm1.ActionStartExecute(Sender: TObject);
begin
ActionStart.Enabled := FALSE;
fWorkerThread := TWorkerThread.Create (Handle, SomeLengthyServerOperation);
end;
procedure TForm1.ActionStartUpdate(Sender: TObject);
begin
ActionStart.Enabled := fWorkerThread = nil;
end;
procedure TForm1.WMThreadFinished(var AMsg: TWMThreadFinishedMsg);
begin
// process results
fWorkerThread := nil;
end;
TWorkerThread释放自己,但作为最后一个动作,它将消息发布到表单(这就是为什么它将窗口句柄作为参数)。在此消息的处理程序中,您将fWorkerThread设置为nil,以便在下一个空闲循环中重新启用该操作。使用TAction意味着您无需关心创建线程的UI元素是什么 - 它们将在创建线程时被禁用,并在线程完成时重新启用。不需要锁定,并且在线程处于活动状态时不能创建新线程。