代码之家  ›  专栏  ›  技术社区  ›  jpfollenius Rob Kennedy

tmonitor同步/application.processmessages

  •  2
  • jpfollenius Rob Kennedy  · 技术社区  · 16 年前

    我又回到了另一个关于线程和同步的问题。设想一个必须执行长时间操作的服务器应用程序,客户机希望其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 回复  |  直到 16 年前
        1
  •  7
  •   chuacw Allen Bauer    13 年前

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

        2
  •  3
  •   mghie    16 年前

    一些评论:

    1. 你的工作线程听起来像你刚重新实现 AsyncCalls . 使用经过尝试和测试的实现可能比编写自己的实现要好(除非您这样做是为了学习效果,或者因为您有非常特殊的要求)。请看两个 阿森卡尔斯 以及 OmniThreadLibrary .

    2. 锁定应该尽可能的短,所以在monitor.enter()和monitor.exit()中对按钮单击的整个反应包装起来似乎是错误的。这也不能真正理解它的用途。

    3. 持有锁时调用application.processmessages()会带来各种令人讨厌的惊喜。如果需要防止代码被重新输入,通常最好像onclick处理程序所做的第一件事一样禁用所有的UI元素,并在处理程序完成后重新启用它们。还要注意,可以从同一线程多次输入锁,它们只能从 倍数 线程。

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

    5. 如果你看一下这段代码,你会发现你可以通过在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;
    

    NetworkerThread释放了自己,但作为最后一个操作,它向表单发送消息(这就是为什么它将窗口句柄作为参数的原因)。在该消息的处理程序中,您将fWorkerThread设置为nil,以便在下一个空闲循环中重新启用该操作。使用触觉意味着您不需要关心创建线程的UI元素是什么——它们都将在创建线程时被禁用,并在线程完成时被重新启用。不需要锁定,并且在线程处于活动状态时无法创建新线程。