[教程]ZAsync 异步编程教程之七:在后台线程中 async/await

在上一节中,我们做了一个简单的 async/await 的例子,这一节我们继续对其进行更详细的解析。

前面我们已经说过,async/await 模式是一种协同式的多任务执行机制,要求处理代码在等待一个操作时(如磁盘IO、网络IO等),显式调用对应的接口(对于 ZAsync ,为 TZAsync.Yield)。否则使用 async/await 机制并不能带来性能的提升。

对于主线程来说,ZAsync 做了一些处理,直接引用 fmx.zbar.async 或 vcl.zbar.async ,ZAsync 会监测 Application.OnIdle 事件,在空闲时触发 TZAsync.Yield,从而保证 async 函数能够正常执行。await 会强制触发 TZAsync.Yield,让出执行权给其它异步作业处理函数。

在讲具体的示例之前,我们首先把异步执行的作业代码贴出来:

procedure TForm3.AsyncProc(ASender: IZAsyncInvoker; AIdent: String; ACheckCancel: Boolean);
var
  T: Cardinal;
begin
  LogMessage(Format('[%s] 开始执行……', [AIdent]));
  T := TThread.GetTickCount;
  while (TThread.GetTickCount - T < 1000) do
  begin
    if ACheckCancel and ASender.IsCanceled then
      break;
    ASender.Yield;
  end;
  if ASender.IsCanceled then
    LogMessage(Format('[%s] 执行被中断.', [AIdent]))
  else
    LogMessage(Format('[%s] 执行结束.', [AIdent]));
end;

这段代码执行时,先输出一个 [XXX] 开始执行……,并且在执行完成后,根据是否被取消,输出执行被中断还是执行结束的提示。这段是我们后面示例的异步作业函数。

我们来看一个简单的例子:

procedure TForm3.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(SLineBreak + '======= ' + TButton(Sender).Text + ' =========' + SLineBreak);
  TThread.CreateAnonymousThread(
    procedure
    begin
      LogMessage('当前线程 Id = ' + TThread.Current.ThreadID.ToString);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 1');
        end);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 2');
        end);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 3');
        end);
    end).Start;
end;

执行上面的代码,你会发现除了输出一个当前线程ID的消息外,我们的异步过程一句没有执行,线程就退出了。

要直接让上面的代码执行,我们有两种处理办法:

  • 最后一个异步调用,由 TZAsync.Async 改为用 TZAsync.AWait
procedure TForm3.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(SLineBreak + '======= ' + TButton(Sender).Text + ' =========' + SLineBreak);
  TThread.CreateAnonymousThread(
    procedure
    begin
      LogMessage('当前线程 Id = ' + TThread.Current.ThreadID.ToString);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 1');
        end);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 2');
        end);
      TZAsync.AWait(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 3');
        end);
    end).Start;
end;
  • 在线程退出前,调用 TZAsync.Yield 来,以触发异步执行前面的异步过程
procedure TForm3.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(SLineBreak + '======= ' + TButton(Sender).Text + ' =========' + SLineBreak);
  TThread.CreateAnonymousThread(
    procedure
    begin
      LogMessage('当前线程 Id = ' + TThread.Current.ThreadID.ToString);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 1');
        end);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 2');
        end);
      TZAsync.Async(
        procedure(ASender: IZAsyncInvoker)
        begin
          AsyncProc(ASender, '后台线程 3');
        end);
      TZAsync.Yield;
    end).Start;
end;

无论那一种都是可以的,我们看一下执行结果:

[注意]

1、 ZAsync 一旦开始调度异步作业,线程只有在所有的异步作业执行结束后才会退出。以前面的代码为例,TZAsync.Yield 交出执行权后,后台3个线程作业执行完成前,线程不会退出。

2、async 函数的执行顺序是按照加入的先后执行,但完成的先后并不一定按照加入的先后执行。实际执行过程中,异步作业函数内部调用 TZAsync.Yield 让出执行权时,别的异步作业函数就会获得完整的控制权,它自己决定什么时候释放这个控制权。

3、async/await 异步作业需要配套作业函数支持让出对应的时间片,用好它可以提升程序的处理能力。但它只是基础,您可能需要自己编写相应的异步作业代码支持。

分享到: