我们都知道Task.Run
方法可以传入一个CancellationToken
,用于取消。可是有多少人真的去了解过当调用CancellationSource.Cancel
方法时,Task
是否真的被取消了
我们做个实验
public static async void Foo()
{
var source = new CancellationTokenSource();
Task.Run(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine("task 运行结束");
}, source.Token);
await Task.Delay(TimeSpan.FromSeconds(3));
source.Cancel();
Console.WriteLine("取消任务");
}
创建上述程序,让一个Task运行5秒后输出”task运行结束”,而在3秒后就进行取消。
令人惊讶的是任务竟然顺利执行完成。
为了进一步确认结果,我们添加一个后续任务,查看下之前任务的结束状态
public static async void Foo()
{
var source = new CancellationTokenSource();
Task.Run(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine("task 运行结束");
}, source.Token).ContinueWith(task => Console.WriteLine(task.Status));
await Task.Delay(TimeSpan.FromSeconds(3));
source.Cancel();
Console.WriteLine("取消任务");
}
结果是RanToCompletion
而不是Canceled
,这说明任务根本没有被取消
我们如果不在取消前等待3秒,并且添加任务运行开始日志
public static async void Foo()
{
var source = new CancellationTokenSource();
Task.Run(() =>
{
Console.WriteLine("task 运行开始");
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine("task 运行结束");
}, source.Token).ContinueWith(task => Console.WriteLine(task.Status));
source.Cancel();
Console.WriteLine("取消任务");
}
任务有被正常取消了
原因
实际上,当Task.Run
的任务真正开始执行后,调用CancellationSource.Cancel
方法并不能取消任务,或者结束调用线程。调用的方法仍然会顺利执行。
那么带有CancellationToken
的方法重载有什么用呢?
1、如第三个例子所示,在任务运行开始之前,调用Cancel
可以直接取消任务,避免额外消耗一个线程
2、当对应的token,在执行体中抛出OperationCanceledException
,(即调用CancellationToken.ThrowIfCancellationRequested()
)可以将其捕获,并且将任务状态置为Canceled
public static async void Foo()
{
var source = new CancellationTokenSource();
Task.Run(() =>
{
Console.WriteLine("task 运行开始");
Thread.Sleep(TimeSpan.FromSeconds(5));
source.Token.ThrowIfCancellationRequested();
Console.WriteLine("task 运行结束");
},source.Token).ContinueWith(task => Console.WriteLine(task.Status));
await Task.Delay(TimeSpan.FromSeconds(3));
source.Cancel();
Console.WriteLine("取消任务");
}
而如果没有使用这个重载,任务会因未捕获异常而终止,并非取消
public static async void Foo()
{
var source = new CancellationTokenSource();
Task.Run(() =>
{
Console.WriteLine("task 运行开始");
Thread.Sleep(TimeSpan.FromSeconds(5));
source.Token.ThrowIfCancellationRequested();
Console.WriteLine("task 运行结束");
}).ContinueWith(task => Console.WriteLine(task.Status));
await Task.Delay(TimeSpan.FromSeconds(3));
source.Cancel();
Console.WriteLine("取消任务");
}
所以我们为Task.Run
添加了CancellationToken
后仍需要,在方法执行时手动判断token是否取消
参考链接:
- c# - How to cancel a running task? - Stack Overflow
- Task cancellation in C# and things you should know about it
- c# - Stop Task when task run - Stack Overflow
- Task.Run Method (System.Threading.Tasks) - Microsoft Docs
- Task Cancellation - Microsoft Docs
- How to: Cancel a Task and Its Children - Microsoft Docs
- How to: Use Parallel.Invoke to Execute Parallel Operations - Microsoft Docs
本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/Task%E7%9C%9F%E7%9A%84%E5%8F%96%E6%B6%88%E4%BA%86%E4%B9%88.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接:
https://xinyuehtx.github.io
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系
。