CancellationToken.None
是一个等同于默认的特殊值,表示这个方法是永远不会被取消的。
实例代码
static async Task CancelableMethodAsync(CancellationToken token)
{
await Task.Delay(1000, token);
throw new ArgumentException();
}
public static async Task IssueCancelRequestAsync()
{
var cts = new CancellationTokenSource();
var task = CancelableMethodAsync(cts.Token);
// 这里,操作在正常运行。
// 发出取消请求。
cts.Cancel();
//(异步地)等待操作结束。
try
{
await task;
// 如运行到这里,说明在取消请求生效前,操作正常完成 。
}
catch (OperationCanceledException ex)
{
// 如运行到这里,说明操作在完成前被取消。
System.Console.WriteLine(ex.GetType().Name);
}
catch (Exception ex)
{
// 如运行到这里,说明在取消请求生效前,操作出错并结束。
System.Console.WriteLine(ex.GetType().Name);
}
}
输出:
TaskCanceledException
1. 取消请求
public static int CancelableMethod(CancellationToken cancellationToken)
{
for (int i = 0; i != 100000; ++i)
{
// cancellationToken.WaitHandle.WaitOne(1000);
Thread.Sleep(1);
// 这里做一些计算工作。
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return 42;
}
2. 超时后取消
public static async Task IssueTimeoutAsync()
{
Stopwatch sw = Stopwatch.StartNew();
try
{
var cts = new CancellationTokenSource();
var token = cts.Token;
cts.CancelAfter(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(4), token);
}
finally
{
System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
}
}
输出:
2004ms
Unhandled Exception: ... ...
只要执行代码时用到了超时,就该使用 CancellationTokenSource
和 CancelAfter
(或者用构造函数)。虽然还有其他途径可实现这个功能,但是使用现有的取消体系是最简单也是最高效的。
3. 取消并行
public class Matrix
{
public void Rotate(float degrees) { }
}
//只做展示
public static void RotateMatrices(IEnumerable<Matrix> matrices, float degrees, CancellationToken token)
{
Parallel.ForEach(matrices, new ParallelOptions
{
CancellationToken = token
},
matrix => matrix.Rotate(degrees));
}
4. 取消响应式代码
注入取消请求
- 某一个层次的代码需要响应取消请求,同时它本身也要向下一层代码发出取消请求(但不会向上传递)。
public static async Task RunGetWithTimeoutAsync()
{
CancellationTokenSource source = new CancellationTokenSource();
await GetWithTimeoutAsync("http://www.baidu.com", source.Token);
}
public static async Task<HttpResponseMessage> GetWithTimeoutAsync(string url, CancellationToken cancellationToken)
{
var client = new HttpClient();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
cts.CancelAfter(TimeSpan.FromMilliseconds(100));
var combinedToken = cts.Token;
return await client.GetAsync(url, combinedToken);
}
}
输出:
Unhandled Exception: Unhandled exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.AggregateException: One or more errors occurred. (A task was canceled.) ---> System.Threading.Tasks.TaskCanceledException ... ...
5. 与其他取消体系的互操作
有一些外部的或以前遗留下来的代码采用了非标准的取消模式。现在要用标准的CancellationToken 来控制这些代码
public static async Task RunPingAsync()
{
var cts = new CancellationTokenSource();
var task = PingAsync("192.168.0.101", cts.Token);
//cts.Cancel();
await task;
}
public static async Task<PingReply> PingAsync(string hostNameOrAddress, CancellationToken cancellationToken)
{
Stopwatch sw = Stopwatch.StartNew();
try
{
var ping = new Ping();
using (cancellationToken.Register(() => ping.SendAsyncCancel()))
{
return await ping.SendPingAsync(hostNameOrAddress);
}
}
finally
{
System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
}
}
注意: 为了避免内存和资源的泄漏,一旦不再需要使用回调函数了,就要释放这个回调函数注册。