Delphi队列和同步

Raf*_*ssi 14 delphi multithreading

我正在网上阅读Nick Hodges,我发现了Queue,但它并没有像我预期的那样表现,我无法理解他和文档所说的内容.看看这段代码:

 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;
Run Code Online (Sandbox Code Playgroud)

我总是使用,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;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,备注输出start但不是结束.我打电话的时候:

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

Rik*_*Rik 19

queue和synchronize之间的区别在于Synchronize()将调用放入队列并等待该调用完成Queue()并将调用放入队列并直接将控制返回给线程.

但是...... 官方文档中没有提到这一点,当一个线程完成时,队列中的所有调用都被删除Queue(AThread, AMethod),其中AThread是自己的线程.

您可以清楚地看到在TThread.Destroy()哪里RemoveQueuedEvents(Self)调用.

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

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

您可以采取一些措施来解决这个问题.

  • 如您在评论中提到的那样,您可以致电TThread.Queue(nil, AMethod).BTW调用TThread.Queue(AMethod)是相同的,TThread.Queue(Self, AMethod)所以如果线程即将结束并且您希望调用完成,您将始终需要使用nil-variant.
  • 但是......如果在执行调用时仍需要激活线程(对于某些数据),则需要阻止线程退出.您可以使用Synchronize()最后一个队列方法来完成此操作.请注意,最后一次同步不必是真正的过程.您可以在TThread.Execute类似的结尾处调用同步到虚拟过程Synchronize(DummySync)(示例).队列如果是FIFO,那么线程将等待直到队列中的所有调用都被处理(包括空dummysync).

可以在这些页面上找到一些额外的信息
确保在线程自毁之前完成所有TThread.Queue方法
http://www.uweraabe.de/Blog/2011/01/30/synchronize-and-queue-with-parameters/

  • 当你将`nil`作为第一个参数而不是`TThread.Current`时,可以避免这种情况. (3认同)
  • @KenWhite,虽然资源不是第一手资源,但我通常信任它:http://www.uweraabe.de/Blog/2011/01/30/synchronize-and-queue-with-parameters/(最后一段).对于仍然不信的人:使用来源. (2认同)
  • @RaffaeleRossi 我可以确认在这种情况下传递 `nil` 有效。我一直这样做。如果您为 `Queue()` 指定一个线程对象,则请求与该线程相关联。当一个线程被终止/释放时,它对自身调用`RemoveQueuedEvents()`,取消任何尚未处理的相关排队请求。将 `nil` 传递给 `Queue()` 会绕过它,因此不会取消请求。 (2认同)