delphi 协程 doroutine 取消协程、及取消回调

简介

协程 执行前、执行中、执行后 全部都可以被取消;

  • 执行前自动取消;
  • 执行中,是协程内核 优先尝试取消,并清空线程栈;若开发者内部是for循环大耗时协程,开发者自己也可以 通过 IsCancel 判断,来自己结束此协程;
  • 执行后取消是指,可能此协程衍生出来了很多子协程,这些子协程又是有 前、中、后 3个状态;子协程会连带自动取消;

示例代码

unit main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, doroutine, System.Generics.Collections;

type
  TFormMain = class(TForm)
    Button1: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FormMain: TFormMain;
  n: Integer;

implementation

{$R *.dfm}

procedure doCanceled(go: TGo);
begin
  if TThread.Current.ThreadID = MainThreadID then
  begin
    ShowMessage('主线程 执行了 取消回调');
  end else begin
    ShowMessage('bg线程 执行了 取消回调');
  end;
end;

procedure bgSmall(go: TGo);
begin
  //doSomeThing http 请求了,或其他 后台业务
  Sleep(100); //模拟做事
  //可看下日志
  OutputDebugString(PChar(Format('干完了第: %d 个,后,由于被取消了,剩下的不干了!', [AtomicIncrement(n)])));
end;

procedure bgBig(go: TGo);
begin
  //此大协程开启300个bg小协程做事
  for var i := 1 to 300 do
  begin
    go.bg(bgSmall).start;
  end;
end;

procedure TFormMain.Button1Click(Sender: TObject);
begin
  //开启一个大协程做事,做的过程中,取消此大协程
  var tid := go.bg(bgBig).onCancelUi(doCanceled).start;

  Sleep(1000); //模拟让当前线程睡一会,让后台【线程们】执行一会,然后开始取消

  gm.cancel(tid); //开始取消这个big协程,它衍生的300个小协程一并都会被取消;
end;

/// <summary>
/// 一个协程内部 又使用了 for 循环,且每次循环 都有耗时操作
/// </summary>
procedure forLoopBg(go: TGo);
begin
  for var i := 1 to 100 do
  begin
    if go.isCancel then
    begin
      OutputDebugString(PChar(Format('循环到:%d后,我被取消了,跳出循环,退出此协程!', [i])));
      Exit; //退出此协程
    end;
    Sleep(100); //模拟耗时做一些事,外层是一个 for 循环
  end;
end;

procedure TFormMain.Button3Click(Sender: TObject);
begin
  //启动一个协程内部有 for 循环 耗时的协程;
  var tid := go.bg(forLoopBg).onCancelUi(doCanceled).start;

  Sleep(1000); //模拟让当前线程睡一会,然后开始取消

  gm.cancel(tid); //开始取消
end;

end.

效果图

image

留意

  1. 开发者应该尽量避免在一个协程内部,又写 【耗时】的 for/while/repeat 循环,不耗时的 for循环 你可以随便写;应该把这个耗时的循环协程,拆分成多个小协程;这个是 推荐的做法,因为这样运行效率是最高的,协程是并行的;

  2. 在耗时循环内部使用 isCancel 判断,若为 true 则退出循环,如上例里的:

    /// <summary>
    /// 一个协程内部 又使用了 for 循环,且每次循环 都有耗时操作
    /// </summary>
    procedure forLoopBg(go: TGo);
    begin
      for var i := 1 to 100 do
      begin
        if go.isCancel then
        begin
          OutputDebugString(PChar(Format('循环到:%d后,我被取消了,跳出循环,退出此协程!', [i])));
          Exit; //退出此协程
        end;
        Sleep(100); //模拟耗时做一些事,外层是一个 for 循环
      end;
    end;
    
posted @ 2025-02-05 17:15  殴阳疯  阅读(95)  评论(0)    收藏  举报