为什么当我调用CancellationTokenSource';时任务没有被取消;s异步方法中的Cancel方法

本文关键字:取消 异步方法 方法 Cancel 任务 调用 CancellationTokenSource 为什么 | 更新日期: 2023-09-27 17:59:07

我围绕CancellationTokenCancellationTokenSource创建了一个小包装。我遇到的问题是CancellationHelperCancelAsync方法没有按预期工作。

我遇到了ItShouldThrowAExceptionButStallsInstead方法的问题。要取消正在运行的任务,它会调用await coordinator.CancelAsync();,但该任务实际上并没有被取消,并且不会在task.Wait 上引发异常

ItWorksWellAndThrowsException似乎工作得很好,它使用了coordinator.Cancel,这根本不是一个异步方法。

问题是,当我在异步方法中调用CancellationTokenSource的Cancel方法时,为什么任务没有被取消?

不要让waitHandle让你感到困惑,这只是为了不让任务提前完成。

让代码自己说话:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace TestCancellation
{
    class Program
    {
        static void Main(string[] args)
        {
            ItWorksWellAndThrowsException();
            //ItShouldThrowAExceptionButStallsInstead();
        }
        private static void ItShouldThrowAExceptionButStallsInstead()
        {
            Task.Run(async () =>
            {
                var coordinator = new CancellationHelper();
                var waitHandle = new ManualResetEvent(false);
                var task = Task.Run(() =>
                {
                    waitHandle.WaitOne();
                    //this works well though - it throws
                    //coordinator.ThrowIfCancellationRequested();
                }, coordinator.Token);
                await coordinator.CancelAsync();
                //waitHandle.Set(); -- with or without this it will throw
                task.Wait();
            }).Wait();
        }
        private static void ItWorksWellAndThrowsException()
        {
            Task.Run(() =>
            {
                var coordinator = new CancellationHelper();
                var waitHandle = new ManualResetEvent(false);
                var task = Task.Run(() => { waitHandle.WaitOne(); }, coordinator.Token);
                coordinator.Cancel();
                task.Wait();
            }).Wait();
        }
    }
    public class CancellationHelper
    {
        private CancellationTokenSource cancellationTokenSource;
        private readonly List<Task> tasksToAwait;
        public CancellationHelper()
        {
            cancellationTokenSource = new CancellationTokenSource();
            tasksToAwait = new List<Task>();
        }
        public CancellationToken Token
        {
            get { return cancellationTokenSource.Token; }
        }
        public void AwaitOnCancellation(Task task)
        {
            if (task == null) return;
            tasksToAwait.Add(task);
        }
        public void Reset()
        {
            tasksToAwait.Clear();
            cancellationTokenSource = new CancellationTokenSource();
        }
        public void ThrowIfCancellationRequested()
        {
            cancellationTokenSource.Token.ThrowIfCancellationRequested();
        }
        public void Cancel()
        {
            cancellationTokenSource.Cancel();
            Task.WaitAll(tasksToAwait.ToArray());
        }
        public async Task CancelAsync()
        {
            cancellationTokenSource.Cancel();
            try
            {
                await Task.WhenAll(tasksToAwait.ToArray());
            }
            catch (AggregateException ex)
            {
                ex.Handle(p => p is OperationCanceledException);
            }
        }
    }
}

为什么当我调用CancellationTokenSource';时任务没有被取消;s异步方法中的Cancel方法

.NET中的取消是协作的。

这意味着持有CancellationTokenSource的一个发出取消信号,持有CancellationToken的一个需要检查是否发出了取消信号(通过轮询CancellationToken或通过注册代理以在发出信号时运行)。

Task.Run中,您使用CancellationToken作为参数,但不在任务本身内部检查它,因此只有在任务有机会启动之前发出令牌时,任务才会被取消。

要在任务运行时取消任务,您需要检查CancellationToken:

var task = Task.Run(() =>
{
    token.ThrowIfCancellationRequested();
}, token);

在您的情况下,您阻塞了ManualResetEvent,因此无法检查CancellationToken。您可以向CancellationToken注册一个委派,以释放重置事件:

token.Register(() => waitHandle.Set())