在异步函数中,一般使用CancellationToken来控制函数的执行。这个Token需要作为参数传递到异步函数中:
- public staic Task
DoAsync(CancellationToken token) - {
- ...
- }
那么如果一个异步函数没有这个Token参数,如何取消呢?
之前看到一个博客:How to Cancel a Non-Cancellable Task in C#
先上代码:
- public static class TaskExtensions
- {
- public static async Task<T> WithCancellation<T> (this Task
task,CancellationToken cancellationToken ) - {
- var tcs=new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- using (cancellationToken.Register(state =>
- {
- ((TaskCompletionSource<object>)state!).TrySetResult(null!);
- }, tcs))
- {
- var resultTask = await Task.WhenAny(task, tcs.Task);
- if(resultTask==tcs.Task)
- {
- throw new OperationCanceledException(cancellationToken);
- }
- return await task;
- }
- }
- }
显然这是一个扩展方法,旨在为Task
代码中首先构建了一个异步任务,利用TaskCompletionSource
然后我们使用这个扩展方法构建实例:
- internal class CancelTaskWithoutCancellationToken
- {
- private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
-
- public static async Task Test()
- {
- try
- {
- var result = await Task.Run(async () =>
- {
- await Task.Delay(TimeSpan.FromSeconds(5));
- Console.WriteLine("操作完成");
- await Task.Delay(300);
- Console.WriteLine("还继续吗?");
- return 7;
- }).WithCancellation(cancellationTokenSource.Token);
- Console.WriteLine(result.ToString());
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- Console.WriteLine("任务取消了");
- }
- finally
- {
- cancellationTokenSource.Dispose();
- }
- }
- public static void Cancel()
- {
- cancellationTokenSource?.Cancel();
- }
- }
测试代码:
- var t1=CancelTaskWithoutCancellationToken.Test();
- var t2 = Task.Run(() =>
- {
- Thread.Sleep(1000);
- CancelTaskWithoutCancellationToken.Cancel();
- });
- await Task.WhenAll(t1, t2);
运行结果如下:
好像结果很符合预期。
假设测试代码后面还有一些任务要运行,也就是主线程没那么快结束呢?让我们在测试代码后面加一行:
- var t1=CancelTaskWithoutCancellationToken.Test();
- var t2 = Task.Run(() =>
- {
- Thread.Sleep(1000);
- CancelTaskWithoutCancellationToken.Cancel();
- });
- await Task.WhenAll(t1, t2);
- Console.WriteLine("----------------------");
- Console.ReadLine();
运行结果:
Oh No,原来的任务还是执行了,说明没能阻止那个任务继续运行,所以原博客说取消一个不能被取消的任务(non-cancelable)是错的,开工没有回头箭。但是从前面的例子,我们可以取消等待那个任务。
实际上博主在另外一篇博客找到了关于这个问题的说明:
How do I cancel non-cancelable async operations?
博主在最后总结:
So, can you cancel non-cancelable operations? No. Can you cancel waits on non-cancelable operations? Sure… just be very careful when you do.
所以,如果想能随时取消一个Task,最稳妥的办法还是将Token作为参数传递进去