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

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

cs 复制代码
public staic Task<T> DoAsync(CancellationToken token)
{
    ...
}

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

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


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

先上代码:

cs 复制代码
    public static class TaskExtensions
    {
        public static async Task<T> WithCancellation<T> (this Task<T> 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<T>类型扩展一个名叫WithCancellation函数。这个函数会有一个CancellationToken,但这个Token不是传递给任务参数task(也就是我们要取消的函数)的,而是用于外部控制。

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

2. 测试

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

cs 复制代码
 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();
        }
    }

测试代码:

cs 复制代码
var t1=CancelTaskWithoutCancellationToken.Test();
var t2 = Task.Run(() =>
{
    Thread.Sleep(1000);
    CancelTaskWithoutCancellationToken.Cancel();
});
await Task.WhenAll(t1, t2);

运行结果如下:

好像结果很符合预期。

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

cs 复制代码
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作为参数传递进去

相关推荐
Dxy123931021624 分钟前
JS DES 加密详解
开发语言·javascript·arcgis
充值内卷25 分钟前
我给桌面加了个鱼缸:C++ 写后端,WebView2 里养一座 Three.js 小岛
开发语言·javascript·c++
馒头不想说话1 小时前
实体店老板的24小时AI运营员工,写、剪、发全自动,省下一整个团队工资
前端
三无少女指南1 小时前
从这里开始:Java 后端与 AI 工程的三条阅读路线
java·学习路线·ai工程
香蕉大香蕉(06敬畏市场)1 小时前
MySQL 单点详解(07)
java·数据库·mysql·ai·博客·八股
nice_lcj5201 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试
骑着蜗牛撵大象3272 小时前
2026 后端 AI 工程化:Spring AI 管调用、MCP 管工具、Agent 内嵌管自治
java·人工智能·后端·spring·ai·可观测性·mcp
tryxr2 小时前
JVM 垃圾回收机制
java·jvm·gc·垃圾回收
椒哥3 小时前
一天被问几十次"允许",我统计了 517 次,然后写了个插件
前端·javascript·github
风骏时光牛马3 小时前
生成一个AI稳定性治理及疑难问题分析标题,只需要一个标题,不需要其他的任何文本。
前端