关于多线程:主线程阻塞并行线程? | 珊瑚贝

Main thread blocking parallel thread?


创建一个 VCL Forms 应用程序,在 Form 上放置一个 TButton 和一个 TMemo,并在按钮的 OnClick 处理程序中编写此代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;

现在运行程序并进行以下测试:

  • 点击按钮,等待循环完成并查看备忘录最后一行显示的时间:应该是大约5300毫秒。

  • 现在再次单击按钮,单击并按住表单的标题栏并快速移动表单直到循环结束。现在再看一下备忘录的最后一行:在我的测试中,时间超过了 7000 毫秒。很明显,主线程阻塞了并行线程!

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

    • -“显然,…” – 为什么?首先,我建议您打开任何 cpu 使用监控程序,然后按住任何窗口的标题栏并快速移动,同时观察它对 cpu 使用的影响。如果它有什么重要意义,那么你的假设是没有实际意义的。
    • Remy,你能举一个小代码示例吗?


    首先,此代码不是线程安全的,因为异步代码直接从主 UI 线程之外的任务线程访问 TMemo。你不能这样做。工作线程必须与 UI 线程同步才能安全地访问 UI 控件,否则可能会发生坏事。您可以使用 TThread.Synchronize()、TThread.Queue() 或 IOmniTask.Invoke() 进行同步。

    其次,当您在标题栏上按住鼠标时,主 UI 消息循环被阻止(操作系统正在运行一个单独的模式消息循环,直到您松开鼠标)。因此,在主消息循环重新获得控制权之前,可能不会运行任务的 OnTerminate 事件处理程序。这将解释为什么您的计时器持续时间据报道比预期的要长,而不是因为任务循环被阻塞。

    第三,Sleep()不是绝对的。它将至少hibernate请求的时间,但可能会hibernate更长时间。因此,您的任务循环将运行至少 5 秒,但可能会更长一点。

    • 关于”从主 UI 线程之外的任务线程访问 TMemo”:我假设此规则仅对修改 UI 控件的工作线程有效,而不适用于从 UI 控件读取值?
    • @user1580348:不。任何访问,读取或写入,都必须同步。主要原因是 TWinControl.Handle 属性不是线程安全的,它可能会在单独的线程使用 HWND 时更改值,这会导致各种问题。因此,即使读取与 HWND 相关的数据也必须受到保护。
    • 您还可以通过 PostMessage() 和自定义消息发送将在备忘录中显示的字符串。
    • @Nat 仅当您将其发布到保证不会在任务线程运行时动态重新分配的 HWND 时,例如 TApplication.Handle,或者您直接使用 AllocateHWnd() 或 CreateWindow/Ex() 分配的 HWND .然后消息处理程序可以将接收到的字符串添加到备忘录中。
    • 但是,如果程序逻辑和程序流程肯定排除了另一个线程与工作线程同时访问 UI 控件怎么办?在这种情况下,不应该允许从工作线程访问 UI 控件吗?
    • @user1580348 不,因为主 UI 线程本身仍然会随机接触控件,例如响应操作系统消息。并且控件的 Handle 属性值可以随时动态重新创建。如果一个工作线程在重新创建开始之后和新的 HWND 准备好之前接触到控件,那么会发生非常糟糕的事情。这是一种具有恶劣后果的竞争条件。所以最好避免它。
    • @RemyLebeau 够公平的。如果只是 UI 更新,那或许你不需要太担心。只需确保任何新句柄在更改时都传递给线程。否则,我经常给我的线程一个从 AllocateHWnd() 到 PostMessage() 的窗口句柄,然后我将一个事件调用回主窗体。


    来源:https://www.codenong.com/41474961/

    微信公众号
    手机浏览(小程序)
    0
    分享到:
    没有账号? 忘记密码?