CLR via 一书中这样写到
创建一个 Task 时,可以将一个 CancellationToken 传递给Task的构造器,从而将这个Cancellation 和该 Task 关联起来。如果 CacellationToken 在 Task 调度前取消, Task 会被取消,永远都不会执行。但是,如果Task 已调度,那么Task 为了允许它的操作在执行期间取消,Task 的代码就必须显示支持取消。
来个试验:
static void Main(string[] args) {
CancellationTokenSource tokenSource = new CancellationTokenSource();
Task task1 = Task.Factory.StartNew(
() => taskRun("1"), tokenSource.Token
);
Thread.Sleep(100);
tokenSource.Cancel();
Thread.Sleep(2000);
Console.WriteLine("task1是否取消:" + task1.IsCanceled);//task1.IsCanceled;
//Console.WriteLine("task2是否取消:" + task2.IsCanceled);
Console.ReadKey();
}
static void taskRun(object obj) {
string id = obj as string;
for (int i = 0; i < 1000; i++) {
Console.WriteLine("运行开始:" + i);
Thread.Sleep(1);
}
}
开启一个任务,并在任务执行100ms 后调用CancellationTokenSource 的Cancel 函数。并在Cancel 执行后 输出 task1 是否取消
这个任务从0打印到999。每次打印后线程睡眠1ms。
结果为任务未被取消!
我把 Sleep(100) 删除后, 也就是开启任务后,马上调用Cancel 函数
从这里可以看出,在一个已经开始执行的任务时,单单使用Cancel 函数是不起作用的。任务根本不会被取消。
取消一个已经运行的任务
方法1:
static void Main(string[] args) {
CancellationTokenSource tokenSource = new CancellationTokenSource();
var tk = tokenSource.Token;
Task task1 = Task.Factory.StartNew(
() => taskRun(tk), tk
);
//Thread.Sleep(100);
tokenSource.Cancel();
Thread.Sleep(2000);
Console.WriteLine("完成!" + task1.IsFaulted + "," + task1.IsCompleted + "," + task1.IsCanceled);
Console.ReadKey();
}
static void taskRun(object obj) {
CancellationToken ct = (CancellationToken)obj;
for (int i = 0; i < 1000; i++) {
Console.WriteLine("运行开始:" + i);
Thread.Sleep(1);
ct.ThrowIfCancellationRequested();
}
}
注意:此代码需要在外部运行,不要使用编辑器调试。(编辑器调试的情况 ct.ThrowIfCancellationRequested 会引发异常,会弹窗中断无法得到正确的结果)。
运行结果
在任务中使用ct.ThrowIfCancellationRequested(); 如果任务在外部取消,将会抛出异常中断任务。在外部可以获得任务 已经完成 并且 已经取消的状态 这个要非常注意。
IsCompleted 属性可能不是 真的完成,还可能被取消了。要配合 IsCanceled 一起使用。
这里使用参数传递 CancellationToken,任务只支持一个参数,如果有多个参数要传递可以把参数包装成类,或者使用字典之类的容器来传递。
方法2:
自己定义取消标记,自己处理。
//是否取消
volatile static bool IsCancel = false;
static void Main(string[] args) {
Task task1 = Task.Factory.StartNew(
()=> taskRun()
);
Thread.Sleep(100);
IsCancel = true;
Thread.Sleep(2000);
Console.WriteLine("完成!" + task1.IsFaulted + "," + task1.IsCompleted + "," + task1.IsCanceled);
//Console.WriteLine("task1是否取消:" + task1.IsCanceled);//task1.IsCanceled;
//Console.WriteLine("task2是否取消:" + task2.IsCanceled);
Console.ReadKey();
}
static void taskRun() {
for (int i = 0; i < 1000; i++) {
Console.WriteLine("运行开始:" + i);
Thread.Sleep(1);
if(IsCancel)
return;
}
}
运行结果
这里使用一个全局字段来控制任务是否取消。
这里要注意的是用来表示任务取消标记的字段需使用volatile 关键字来标记字段为易变类型,否则可能无法正常取消任务!
每次从属性CancellationTokenSource.Token获得的CancellationToken(值类型)是不是同一个值?
CancellationTokenSource cts = new CancellationTokenSource();
var tk1 = cts.Token;
var tk2 = cts.Token;
if(tk1 == tk2)
Console.WriteLine("同一个Token!");
else
Console.WriteLine("不是同一个Token!");
运行后得到 “同一个Token!”
两个任务使用同一个CancellationTokenSource 取消任务会是怎么的结果?
static void Main(string[] args) {
CancellationTokenSource cts = new CancellationTokenSource();
var tk1 = cts.Token;
var tk2 = cts.Token;
Task task1 = Task.Factory.StartNew(
()=> taskRun(new List
我开启了两个任务,两个任务使用同一个CancelltionTokenSource以及同一个CancelltionToken,在第一个任务中使用 tk.ThrowIfCancellationRequested(); 来显示取消任务。当两个任务在运行中,调用 cancel() 后,第一个任务被成功取消,第二个任务未被取消。这个结果非常有趣,两个任务使用的是同一个Token, 却只取消调用ThrowIfCancellationRequested() 函数的任务! (我猜测是ThrowIfCancellationRequested函数会返回当前运行的线程,然后把区分任务,把当前任务停止!)
任务被取消,我读取任务的返回值会发生什么?
如果任务已被取消,使用返回值会抛出 System.AggregateException 异常。
任务取消是异步行为,使用Wait等待任务完成或取消会发生什么?
如果任务已被取消,使用Wait同样抛出 System.AggregateException 异常