当我在异步方法中调用CancellationTokenSource的Cancel方法时,为什么不取消任务?

bol*_*oli 6 c# asynchronous task cancellationtokensource cancellation-token

我创造了一个小包装CancellationTokenCancellationTokenSource.我遇到的问题是CancelAsync方法CancellationHelper不能按预期工作.

我遇到了这个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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

i3a*_*non 8

.Net中的取消是合作的.

这意味着持有CancellationTokenSource信号取消的那个和CancellationToken需要检查取消是否被发信号的那个(通过轮询CancellationToken或通过在发信号时注册代表来运行).

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

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

var task = Task.Run(() =>
{
    token.ThrowIfCancellationRequested();
}, token);
Run Code Online (Sandbox Code Playgroud)

在你的情况下你阻止了,ManualResetEvent所以你将无法检查CancellationToken.您可以将代理注册到CancellationToken释放重置事件的代理:

token.Register(() => waitHandle.Set())
Run Code Online (Sandbox Code Playgroud)