主线程阻塞并行线程?

3
创建一个VCL表单应用程序,在表单上放置一个TButton和一个TMemo,然后在按钮的OnClick处理程序中编写以下代码:
uses
  OtlParallel, OtlTaskControl;

procedure TForm2.btnStartLoopClick(Sender: TObject);
var
  starttime: Cardinal;
  k: Integer;
begin
  mmoTest.Lines.Clear;
  for k := 1 to 50 do
    mmoTest.Lines.Add('Line ' + IntToStr(k));

  starttime := GetTickCount;
  Parallel.Async(
    procedure
    var
      i: Integer;
    begin
      for i := 1 to 50 do
      begin
        Sleep(100);
        mmoTest.Lines[i - 1] := mmoTest.Lines[i - 1] + FormatDateTime(' nn:ss:zzz', Now);
      end;
    end,
    Parallel.TaskConfig.SetPriority(TOTLThreadPriority.tpHighest).OnTerminated(
    procedure
    begin
      mmoTest.Lines.Add(IntToStr(GetTickCount - starttime) + ' milliseconds');
    end));
end;

现在运行程序并进行以下测试:
  1. 点击按钮,等待循环完成后查看备忘录中显示的时间:应该约为5300毫秒。

  2. 现在再次点击按钮,在循环完成之前单击并按住窗体的标题栏,并快速移动窗体。现在再次查看备忘录的最后一行:在我的测试中,时间超过了7000毫秒。显然,主线程正在阻塞并行线程!

那么如何避免主线程阻塞并行线程呢?

1
显然,为什么?首先,我建议您进行测试,打开任何CPU使用率监控程序,然后保持任何窗口的标题栏并快速移动,观察它如何影响CPU使用率。如果有任何显著影响,那么您的假设就是无效的。 - Sertac Akyuz
Remy,你能给一个小的代码示例吗? - user1580348
1个回答

10
首先,这段代码不是线程安全的,因为异步代码直接从主 UI 线程外的任务线程访问 TMemo。你不能这样做。工作线程必须与 UI 线程同步,以便安全地访问 UI 控件,否则可能会发生意想不到的错误。你可以使用 TThread.Synchronize()TThread.Queue() 或者 IOmniTask.Invoke() 进行同步。
其次,当你按住标题栏时,主 UI 消息循环会被阻塞(操作系统会运行一个单独的模态消息循环,直到你松开鼠标)。因此,任务的 OnTerminate 事件处理程序可能要等到主消息循环重新获得控制才能执行。这解释了为什么计时器持续时间比预期长,而不是因为任务循环被阻塞。
第三,Sleep() 不是绝对可靠的。它会至少休眠所请求的时间,但可能会休眠更长的时间。因此,你的任务循环将至少运行 5 秒钟,但可能会稍微长一点。

5
@user1580348: 不管是访问、读取还是写入,都必须进行同步。这样做的主要原因是TWinControl.Handle属性不是线程安全的,当单独的线程使用HWND时,它的值可能会发生变化,从而导致各种问题。因此,即使是与HWND相关联的数据的读取也必须得到保护。 - Remy Lebeau
1
您还可以使用PostMessage()和自定义消息来发送要在备忘录中显示的字符串。 - Nat
1
只有当您将其发布到在任务线程运行时保证不会动态重新分配的 HWND,例如 TApplication.Handle 或使用 AllocateHWnd()CreateWindow/Ex() 直接分配的 HWND 时,才能使用该方法。然后,消息处理程序可以将接收到的字符串添加到备忘录中。 - Remy Lebeau
但是,如果程序逻辑和程序流程确保另一个线程不会同时访问UI控件,那么怎么办?在这种情况下,应该允许从工作线程访问UI控件吗? - user1580348
1
@user1580348 不行,因为主 UI 线程本身仍然会在随机时间触摸控件,例如响应操作系统消息。而且控件的 Handle 属性值可以在任何时候动态重新创建。如果工作线程在重新创建开始后并在新的 HWND 准备好之前触摸控件,则会发生非常糟糕的事情。这是一种具有恶劣后果的竞争条件。所以最好避免它。 - Remy Lebeau
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接