CancellationTokenSource
是 .NET 中用于实现任务或异步操作的 取消机制 的一个核心类。通过它,我们可以向异步任务发送取消信号,从而安全地终止任务的执行,避免不必要的资源消耗。
核心概念
CancellationTokenSource
:- 用于发出取消信号的类。
- 可以通过它生成一个
CancellationToken
,将其传递给任务或异步方法。 - 当调用
Cancel
方法时,表示发出了取消信号,任务会根据此信号选择是否终止。
CancellationToken
:- 由
CancellationTokenSource
提供,用于监听取消信号。 - 异步任务可以通过检查
IsCancellationRequested
属性或调用ThrowIfCancellationRequested
方法来检测取消信号。
- 由
基本用法
示例代码:使用 CancellationTokenSource
取消任务
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// 创建 CancellationTokenSource
CancellationTokenSource cts = new CancellationTokenSource();
// 获取 CancellationToken
CancellationToken token = cts.Token;
// 启动任务
var task = Task.Run(() => DoWork(token), token);
Console.WriteLine("Press Enter to cancel the operation...");
Console.ReadLine();
// 发送取消信号
cts.Cancel();
try
{
// 等待任务完成
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled.");
}
finally
{
cts.Dispose(); // 释放资源
}
}
static void DoWork(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
// 检查是否收到取消信号
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // 模拟工作
}
}
}
输出示例
如果未取消任务:
Working... 0
Working... 1
Working... 2
...
Working... 9
2.如果按下回车取消任务:
Working... 0
Working... 1
Working... 2
Task was cancelled.
核心功能和方法
CancellationTokenSource
方法
方法 | 描述 |
---|---|
Cancel() |
发出取消信号。所有与该 CancellationTokenSource 关联的 CancellationToken 会收到信号。 |
CancelAfter(int delay) |
在指定的毫秒数后发出取消信号。 |
Dispose() |
释放 CancellationTokenSource 使用的资源。 |
CancellationToken
属性和方法
属性/方法 | 描述 |
---|---|
IsCancellationRequested |
表示是否已请求取消操作。 |
ThrowIfCancellationRequested() |
如果收到取消信号,则抛出 OperationCanceledException 异常。 |
Register(Action callback) |
注册一个回调函数,当取消信号被发出时会调用此回调。 |
高级用法
1. 超时自动取消
可以通过 CancelAfter
方法设置超时,在指定时间后自动取消任务。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
// 设置超时时间为 5 秒
cts.CancelAfter(5000);
try
{
await Task.Run(() => DoWork(cts.Token), cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled due to timeout.");
}
}
static void DoWork(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // 模拟工作
}
}
}
输出示例
Working... 0
Working... 1
Working... 2
Working... 3
Working... 4
Task was cancelled due to timeout.
2. 多个任务共享取消信号
多个任务可以共享同一个 CancellationToken
,当发出取消信号时,所有任务都会被取消。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var task1 = Task.Run(() => DoWork("Task 1", token), token);
var task2 = Task.Run(() => DoWork("Task 2", token), token);
Console.WriteLine("Press Enter to cancel all tasks...");
Console.ReadLine();
cts.Cancel(); // 取消所有任务
try
{
await Task.WhenAll(task1, task2);
}
catch (OperationCanceledException)
{
Console.WriteLine("All tasks were cancelled.");
}
}
static void DoWork(string name, CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"{name} is working... {i}");
Thread.Sleep(1000); // 模拟工作
}
}
}
输出示例
如果未取消:
Task 1 is working... 0
Task 2 is working... 0
Task 1 is working... 1
Task 2 is working... 1
...
2.如果取消:
Task 1 is working... 0
Task 2 is working... 0
Task 1 is working... 1
Task 2 is working... 1
All tasks were cancelled.
3. 嵌套的 CancellationTokenSource
可以创建多个 CancellationTokenSource
,并将它们关联到一个父 CancellationTokenSource
。当父级取消时,所有子级都将取消。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource parentCts = new CancellationTokenSource();
CancellationTokenSource childCts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token);
var task = Task.Run(() => DoWork(childCts.Token), childCts.Token);
Console.WriteLine("Press Enter to cancel parent token...");
Console.ReadLine();
parentCts.Cancel(); // 取消父级,子级也会被取消
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled.");
}
}
static void DoWork(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // 模拟工作
}
}
}
输出示例
如果未取消
Working... 0
Working... 1
Working... 2
...
2.如果取消:
Working... 0
Working... 1
Task was cancelled.
注意事项
取消是协作的:
CancellationTokenSource
只是发出取消信号,任务本身需要主动响应取消(例如通过检查IsCancellationRequested
或调用ThrowIfCancellationRequested
)。- 如果任务忽略了取消信号,它仍然会继续运行。
Dispose
的使用:- 使用完
CancellationTokenSource
后,应该调用Dispose
方法以释放资源,特别是在频繁创建和销毁时。
- 使用完
异常处理:
- 当
ThrowIfCancellationRequested
被调用时,会抛出OperationCanceledException
。这种异常通常用于标识任务的正常取消,而不是错误。
- 当
线程安全:
CancellationToken
和CancellationTokenSource
是线程安全的,可以在多个线程之间安全地共享。
总结
CancellationTokenSource
和CancellationToken
提供了优雅的任务取消机制。- 它们适用于长时间运行的任务、异步操作或需要动态中断的场景。
- 通过合理使用
CancellationTokenSource
和CancellationToken
,可以让你的程序更加高效,避免无谓的资源浪费。