主线程阻塞并行线程?
Main thread blocking parallel thread?
创建一个 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;
现在运行程序并进行测试:
点击按钮,等待循环完成,看备忘录最后一行显示的时间:应该是5300毫秒左右。
现在再次单击该按钮,单击并按住窗体的标题栏并快速移动窗体直到循环结束。现在再看看备忘录的最后一行:在我的测试中,时间超过了 7000 毫秒。很明显,主线程阻塞了并行线程!
那么如何避免主线程阻塞并行线程呢?
首先,此代码不是 thread-safe,因为异步代码直接从主 UI 线程之外的任务线程访问 TMemo
。你不能这样做。工作线程必须与 UI 线程同步,以便安全地访问 UI 控件,否则可能会发生不好的事情。您可以使用 TThread.Synchronize()
, TThread.Queue()
, or IOmniTask.Invoke()
进行同步。
其次,当您在标题栏上按住鼠标时,主 UI 消息循环被阻止(一个单独的模态消息循环被 运行 OS直到松开鼠标)。因此,在主消息循环重新获得控制之前,任务的 OnTerminate
事件处理程序可能不会 运行。这可以解释为什么你的计时器持续时间据报道比预期的要长,而不是因为任务循环被阻止。
第三,Sleep()
不是绝对的。它会休眠 至少 请求的时间,但可能会休眠更长时间。因此,您的任务循环将 运行 持续 至少 5 秒,但可能会更长一些。
创建一个 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;
现在运行程序并进行测试:
点击按钮,等待循环完成,看备忘录最后一行显示的时间:应该是5300毫秒左右。
现在再次单击该按钮,单击并按住窗体的标题栏并快速移动窗体直到循环结束。现在再看看备忘录的最后一行:在我的测试中,时间超过了 7000 毫秒。很明显,主线程阻塞了并行线程!
那么如何避免主线程阻塞并行线程呢?
首先,此代码不是 thread-safe,因为异步代码直接从主 UI 线程之外的任务线程访问 TMemo
。你不能这样做。工作线程必须与 UI 线程同步,以便安全地访问 UI 控件,否则可能会发生不好的事情。您可以使用 TThread.Synchronize()
, TThread.Queue()
, or IOmniTask.Invoke()
进行同步。
其次,当您在标题栏上按住鼠标时,主 UI 消息循环被阻止(一个单独的模态消息循环被 运行 OS直到松开鼠标)。因此,在主消息循环重新获得控制之前,任务的 OnTerminate
事件处理程序可能不会 运行。这可以解释为什么你的计时器持续时间据报道比预期的要长,而不是因为任务循环被阻止。
第三,Sleep()
不是绝对的。它会休眠 至少 请求的时间,但可能会休眠更长时间。因此,您的任务循环将 运行 持续 至少 5 秒,但可能会更长一些。