【问题标题】:Why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?为什么我在异步方法中调用 CancellationTokenSource 的 Cancel 方法时任务没有取消?
【发布时间】:2015-06-22 08:47:25
【问题描述】:

我围绕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);
            }
        }
    }
}

【问题讨论】:

  • 你在哪里打电话CancellationHelper.ThrowIfCancellationRequested()
  • 我相信对canellation令牌的工作方式存在误解......即对于Task.Run,令牌仅在任务构建期间相关,运行时,您必须自己检查令牌.. .
  • @AndreasNiedermair 我明白你在说什么,但是如果任务完成并且在执行过程中的某个时刻调用了CancellationTokenSource.Cancel,那么它的task.Result 应该是TaskStatus.Canceled 不应该吗?
  • @AndreasNiedermair 如果我理解正确,一旦任务开始执行task.Status 就不会是TaskStatus.Canceled,即使Cancel 被调用,我必须检查CancellationTokenSource.IsCancellationRequested

标签: c# asynchronous task cancellationtokensource cancellation-token


【解决方案1】:

.NET 中的取消是合作的。

这意味着持有CancellationTokenSource 的人发出取消信号,而持有CancellationToken 的人需要检查是否已发出取消信号(通过轮询CancellationToken 或注册一个委托以在发出信号时运行) .

在您的Task.Run 中,您使用CancellationToken 作为参数,但您不会在任务本身内部检查它,因此只有在任务必须有机会开始之前发出令牌信号时,才会取消任务.

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

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

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

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

【讨论】:

  • var task = Task.Run(() => { token.ThrowIfCancellationRequested(); }, token);看起来不错。但是在哪里捕获异常。
猜你喜欢
  • 2012-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-11
相关资源
最近更新 更多