TMonitor同步/ Application.ProcessMessages

时间:2009-02-20 15:41:55

标签: multithreading delphi synchronization delphi-2009 tmonitor

我回来了另一个关于线程和同步的问题。想象一下,服务器应用程序必须执行冗长的操作,并且客户端希望他的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,那么锁不起作用?

目前我无法更好地解释。我希望有人明白我的观点。如果没有,请随时提问。

编辑:对于禁用和启用按钮:我对客户端代码一无所知。可能是一个按钮事件处理程序,可能是其他东西。基本上我想隐藏客户端代码的锁定。

2 个答案:

答案 0 :(得分:7)

TMonitor仅阻止不同的线程获取锁定。发生的事情是:通过处理来自锁内的消息,您将返回到同一线程中的相同函数,这导致递归获取锁。然后,您的代码将创建一个新的工作线程,并开始全面循环。您可以禁用该按钮,以便在工作线程完成之前不能再次单击它。确保在开始处理消息之前禁用按钮,然后使用另一个try..finally块以确保重新启用它。根据其余代码的排列方式,您可能甚至不需要锁定。

答案 1 :(得分:3)

一些意见:

  1. 您的WorkerThread听起来就像您刚刚重新实现AsyncCalls。使用经过试验和测试的实现可能比编写自己的实现更好(除非您为了学习效果或因为您有非常特殊的要求而这样做)。请查看AsyncCallsOmniThreadLibrary

  2. 锁定应尽可能短,因此将整个反应包含在Monitor.Enter()和Monitor.Exit()中的按钮单击似乎是错误的。它服务的目的也是不可理解的。

  3. 在锁定时调用Application.ProcessMessages()可能会引入各种令人讨厌的意外。如果您需要保持代码不被重新输入,通常最好禁用所有UI元素作为OnClick处理程序的第一件事,并在处理程序完成时重新启用它们。还要注意,可以从同一个线程多次输入锁,它们只能实现多个线程的独占访问。

  4. 所有VCL都在主GUI线程中执行,因此只有在从后台线程调用相同代码时才需要锁定。

  5. 如果您查看此代码,您将看到通过在GUI线程中完成工作而不是生成工作线程,您可以实现相同的目标。

  6. 我已经在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元素是什么 - 它们将在创建线程时被禁用,并在线程完成时重新启用。不需要锁定,并且在线程处于活动状态时不能创建新线程。