【问题标题】:Delphi Queue and SynchronizeDelphi 队列和同步
【发布时间】:2017-02-16 17:31:33
【问题描述】:

我正在在线阅读 Nick Hodges,我发现了队列,但它的行为与我预期的不同,我无法理解他和文档所说的内容。看这段代码:

 TThread.CreateAnonymousThread(
  procedure
   begin

     TThread.Queue(TThread.Current, procedure
                                          begin
                                           Memo1.Lines.Clear;
                                           Memo1.Lines.Add('start');
                                          end);

     Sleep(2000);

     TThread.Synchronize(TThread.Current, procedure
                                          begin
                                           Memo1.Lines.Add('end');
                                          end);

   end
 ).Start;

我总是使用Synchronize,但这次我尝试使用Queue,因为根据尼克的说法,在多个请求的情况下会更好,因为它们不会被“序列化”并一一执行。上面的代码工作正常。为什么这不起作用?

 TThread.CreateAnonymousThread(
  procedure
   begin

     TThread.Queue(TThread.Current, procedure
                                          begin
                                           Memo1.Lines.Clear;
                                           Memo1.Lines.Add('start');
                                          end);

     Sleep(2000);

     TThread.Queue(TThread.Current, procedure
                                          begin
                                           Memo1.Lines.Add('end');
                                          end);

   end
 ).Start;

在这种情况下,备忘录输出start,但不是结尾。当我打电话时:

  • 第一个同步,第二个同步
  • 第一个队列,第二个同步
  • 两次排队都不起作用,因为我在备忘录中只看到了start

【问题讨论】:

    标签: multithreading delphi


    【解决方案1】:

    队列和同步的区别在于Synchronize()将调用放入队列并等待调用完成,Queue()将调用放入队列并直接将控制权返回给线程。

    然而……这在官方文档中没有提到,当一个线程完成时,所有的调用都放在Queue(AThread, AMethod)的队列中,其中AThread是它的自己的 线程,被删除。

    您可以在TThread.Destroy() 的源代码中清楚地看到,其中调用了RemoveQueuedEvents(Self)

    RemoveQueuedEvents 删除排队的方法调用。 [...] 如果指定了 AThread,则该线程排队的所有方法调用都将被删除。

    因此,在您的最后一个 Queue() 线程结束后,立即执行 TThread.Destroy() 并且最后一个调用从队列中删除。

    你可以做一些事情来解决这个问题。

    • 如 cmets 中所述,您可以致电 TThread.Queue(nil, AMethod)。顺便提一句。调用 TThread.Queue(AMethod)TThread.Queue(Self, AMethod) 相同,因此如果线程即将结束并且您希望调用结束,您将始终需要使用 nil 变体。
    • 但是...如果您在执行调用时仍然需要线程处于活动状态(对于其中的一些数据),您需要阻止线程退出。您可以通过使用Synchronize() 作为最后一个队列方法来做到这一点。请注意,最后一次同步不必是真正的过程。您可以在TThread.Execute 的末尾调用同步到一个虚拟程序,例如Synchronize(DummySync) (example)。如果队列是 FIFO,那么线程将等待,直到队列中的所有调用都被处理(包括空的 dummysync)。

    可以在这些页面上找到一些额外的信息
    Ensure all TThread.Queue methods complete before thread self-destructs
    http://www.uweraabe.de/Blog/2011/01/30/synchronize-and-queue-with-parameters/

    【讨论】:

    • 当您将nil 作为第一个参数而不是TThread.Current 时,可以避免这种情况。
    • @KenWhite,虽然资源不是第一手的,但我通常相信它:) uweraabe.de/Blog/2011/01/30/…(最后一段)。对于仍然不信的人:使用来源。
    • @RaffaeleRossi,正如我在对此答案的第一条评论中提到的那样,您也可以使用 nil 而不是 TThread.Current 来使其与 Queue 一起使用。
    • @RaffaeleRossi 我可以确认通过nil 在这种情况下有效。我一直这样做。如果您为Queue() 指定一个线程对象,则请求与该线程相关联。当一个线程被终止/释放时,它会调用自己的RemoveQueuedEvents(),取消任何尚未处理的相关排队请求。将 nil 传递给 Queue() 会绕过它,因此不会取消请求。
    • @DavidHeffernan 我为使用引用方法而道歉。我实际上并没有引用官方消息来源,而是想传达一个关于使用 Queue() 的已知声明。我确实检查了源中声明的有效性(我现在将其包含在我的答案中)。我可能不应该使用引号。我更改/扩展了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    相关资源
    最近更新 更多