• C# 取消一个不带CancellationToken的任务?


            在异步函数中,一般使用CancellationToken来控制函数的执行。这个Token需要作为参数传递到异步函数中:

    1. public staic Task DoAsync(CancellationToken token)
    2. {
    3. ...
    4. }

            那么如果一个异步函数没有这个Token参数,如何取消呢?

            之前看到一个博客:How to Cancel a Non-Cancellable Task in C#


    1.构建取消异步函数的扩展方法

            先上代码:

    1. public static class TaskExtensions
    2. {
    3. public static async Task<T> WithCancellation<T> (this Task task,CancellationToken cancellationToken)
    4. {
    5. var tcs=new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
    6. using (cancellationToken.Register(state =>
    7. {
    8. ((TaskCompletionSource<object>)state!).TrySetResult(null!);
    9. }, tcs))
    10. {
    11. var resultTask = await Task.WhenAny(task, tcs.Task);
    12. if(resultTask==tcs.Task)
    13. {
    14. throw new OperationCanceledException(cancellationToken);
    15. }
    16. return await task;
    17. }
    18. }
    19. }

            显然这是一个扩展方法,旨在为Task类型扩展一个名叫WithCancellation函数。这个函数会有一个CancellationToken,但这个Token不是传递给任务参数task(也就是我们要取消的函数)的,而是用于外部控制。

            代码中首先构建了一个异步任务,利用TaskCompletionSource,可以构建一个用于控制原任务的异步任务,这里的TaskCompletionSource不多解释了,可以参考博客:here。然后通过传入的Token注册一个回调函数,回调函数传入的参数就是刚刚创立的TaskCompletionSource对象,回调函数会调用成员函数TrySetResult()给任务赋值。而回调函数的执行则是在Token被取消时触发。

    2. 测试

            然后我们使用这个扩展方法构建实例:

    1. internal class CancelTaskWithoutCancellationToken
    2. {
    3. private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    4. public static async Task Test()
    5. {
    6. try
    7. {
    8. var result = await Task.Run(async () =>
    9. {
    10. await Task.Delay(TimeSpan.FromSeconds(5));
    11. Console.WriteLine("操作完成");
    12. await Task.Delay(300);
    13. Console.WriteLine("还继续吗?");
    14. return 7;
    15. }).WithCancellation(cancellationTokenSource.Token);
    16. Console.WriteLine(result.ToString());
    17. }
    18. catch (Exception ex)
    19. {
    20. Console.WriteLine(ex.Message);
    21. Console.WriteLine("任务取消了");
    22. }
    23. finally
    24. {
    25. cancellationTokenSource.Dispose();
    26. }
    27. }
    28. public static void Cancel()
    29. {
    30. cancellationTokenSource?.Cancel();
    31. }
    32. }

    测试代码:

    1. var t1=CancelTaskWithoutCancellationToken.Test();
    2. var t2 = Task.Run(() =>
    3. {
    4. Thread.Sleep(1000);
    5. CancelTaskWithoutCancellationToken.Cancel();
    6. });
    7. await Task.WhenAll(t1, t2);

    运行结果如下:

    好像结果很符合预期。

    假设测试代码后面还有一些任务要运行,也就是主线程没那么快结束呢?让我们在测试代码后面加一行:

    1. var t1=CancelTaskWithoutCancellationToken.Test();
    2. var t2 = Task.Run(() =>
    3. {
    4. Thread.Sleep(1000);
    5. CancelTaskWithoutCancellationToken.Cancel();
    6. });
    7. await Task.WhenAll(t1, t2);
    8. Console.WriteLine("----------------------");
    9. 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作为参数传递进去

  • 相关阅读:
    Unity-3D模型展示
    Linux系统中的配置文件和环境变量
    java 微信小程序Android 智慧老年人养老院管理系统
    怎么用电脑开发安卓app?能外包吗?
    python如何不生成pyc文件(三种方式)
    【无标题】win10 server 服务器,安装mongodb时,遇到无法定位程序输入点BCryptHash , 降低mongodb 版本 4.2.3
    6.13 - 特殊含义的IP地址
    iTOP-3568开发板Ubuntu下安装ADB工具
    OJ练习第172题——可以攻击国王的皇后
    JAVA NIO详解
  • 原文地址:https://blog.csdn.net/q__y__L/article/details/133910221