简介
协程 执行前、执行中、执行后 全部都可以被完美正确的取消;
- 执行前取消 这个好理解;
- 执行中,是协程内核 尝试取消;若开发者内部是大耗时协程,开发者自己也可以 通过 IsCancel 判断来结束协程;
- 执行后,但是有可能衍生出来了很多子协程,这些子协程又是有 前、中、后 3个状态;子协程会连带取消;
协程的取消 通常是不需要开发者关注的,开发者正常取消就可;若你非要写出 大耗时的协程,可以参见 本文扩展部分;
取消回调有2种写法
-
task.bg(bgBig).onCancelUi(doCanceled).start; 创建协程时,就指定取消回调;
然后 真正取消的时候,只需要调用 tasks.cancel() 就可;
-
tasks.cancelWithOnUi(taskId, doCanceled);真正取消的时候,动态指定取消回调;
示例代码
unit main;interfaceusesWinapi.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;typeTForm3 = class(TForm)Button1: TButton;Button2: TButton;procedure Button1Click(Sender: TObject);procedure Button2Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end;varForm3: TForm3;n: Integer;implementation{$R *.dfm}procedure doCanceled(task: TTask);
beginif TThread.Current.ThreadID = MainThreadID thenbeginShowMessage('主线程 执行了 取消回调');end else beginShowMessage('bg线程 执行了 取消回调');end;
end;procedure bgSmall(task: TTask);
begin//doSomeThing http 请求了,或其他 后台业务Sleep(100); //模拟做事//可看下日志OutputDebugString(PChar(Format('干完了第: %d 个,后,由于被取消了,剩下的不干了!', [AtomicIncrement(n)])));
end;procedure bgBig(task: TTask);
begin//此大协程开启300个bg小协程做事for var i := 1 to 300 dobegintask.bg(bgSmall).start;end;
end;procedure TForm3.Button1Click(Sender: TObject);
beginn := 0; //初始化干了第几个后被取消;//开启一个大协程做事,做的过程中,取消此大协程var tid := task.bg(bgBig).onCancelUi(doCanceled).start; //写法1 创建时候,就指定了取消回调Sleep(1000); //模拟让当前线程睡一会,让后台【线程们】执行一会,然后开始取消// tid := '123'; //可以模拟取消失败;if not tasks.cancel(tid) thenbegin//取消失败,你可以 doSomeThing 做一些事ShowMessage('取消失败');end; // else 取消成功,就会执行 取消成功的回调函数,你这里不需要写什么了,要在取消回调函数里写;
end;procedure TForm3.Button2Click(Sender: TObject);
beginn := 0; //初始化干了第几个后被取消;var tid := task.bg(bgBig).start;Sleep(1000); //模拟让当前线程睡一会,让后台【线程们】执行一会,然后开始取消if not tasks.cancelWithOnUi(tid, doCanceled) then //写法2,创建协程时,不指定取消回调,而是取消时动态指定begin//取消失败,你可以 doSomeThing 做一些事ShowMessage('取消失败');end;
end;end.
效果图
扩展
若开发者在一个协程内部出现耗时的循环操作,这样会影响取消的响应速度哦,当然若你这个协程,没有取消的需求,你可以不关注这里;这里并不是那么重要,你即使出现下面这样耗时的 forloop 循环协程,也无伤大雅,只是一个小细节,举例:
procedure forLoopBg(task: TTask);
begin//此大协程开启300个bg小协程做事for var i := 1 to 300 dobeginSleep(1000); //模拟耗时做一些事,外层是一个 for 循环task.bg(bgSmall).start;end;
end;
可见 这个 forLoopBg 协程,是一个非常耗时的协程,长时间不会退出,若你要取消它,协程内核首先会尝试取消它,并释放栈内存;开发者也可以自己通过 isCancel来自我取消,如以下 2 种 解决方案:
-
把这个耗时的 forloop 循环协程,拆分成多个小协程;这个其实是 运行效率最高的,协程是并行的;
-
在循环内部使用 isCancel 判断下,若为 true 则退出循环,比如修改成 下面这样:
procedure forLoopBg(task: TTask); begin//此大协程开启300个bg小协程做事for var i := 1 to 300 dobeginif task.isCancel thenbeginExit; //或 Break 跳出循环;end;Sleep(1000); //模拟耗时做一些事,外层是一个 for 循环task.bg(bgSmall).start;end; end;