为什么在异步方法中调用CancellationTokenSource的Cancel方法时,任务没有被取消? [英] Why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?

查看:463
本文介绍了为什么在异步方法中调用CancellationTokenSource的Cancel方法时,任务没有被取消?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 CancellationToken CancellationTokenSource 周围创建了一个小包装。我遇到的问题是 CancellationHelper CancelAsync 方法无法按预期工作。



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



ItWorksWellAndThrowsException 似乎运行良好,并且使用了 coordinator.Cancel ,这根本不是异步方法。



为什么我打电话时任务未取消的问题 CancellationTokenSource 在异步方法中的Cancel方法?



不要让 waitHandle 会让您感到困惑,这只是因为不让任务提早完成。



让代码说明一切:

 使用系统; 
使用System.Collections.Generic;
使用System.Threading;
使用System.Threading.Tasks;

名称空间TestCancellation
{
类程序
{
静态void Main(string [] args)
{
ItWorksWellAndThrowsException( );
// ItShouldThrowAExceptionButStallsInstead();
}

私有静态无效ItShouldThrowAExceptionButStallsInstead()
{
Task.Run(async()=>
{
var协调器= new CancellationHelper();
var waitHandle = new ManualResetEvent(false);

var task = Task.Run(()=>
{
waitHandle.WaitOne ();

//效果很好-抛出
//coordinator.ThrowIfCancellationRequested();

},coordinator.Token);

await coordinator.CancelAsync();
//waitHandle.Set();-不管有没有这个都会抛出
task.Wait();
})。等待();
}

私有静态无效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();
}
}

公共类CancellationHelper
{
private CancellationTokenSource cancelleTokenSource;
私有只读列表< Task> taskToAwait;

public CancellationHelper()
{
cancelleTokenSource = new CancellationTokenSource();
taskToAwait = new List< Task>();
}

public CancellationToken令牌
{
get {返回cancelTokenTokenSource.Token; }
}

public void AwaitOnCancellation(任务任务)
{
if(task == null)return;

taskToAwait.Add(任务);
}

public void Reset()
{
taskToAwait.Clear();
cancelleTokenSource =新的CancellationTokenSource();
}

public void ThrowIfCancellationRequested()
{
cancellingTokenSource.Token.ThrowIfCancellationRequested();
}

public void Cancel()
{
cancelleTokenSource.Cancel();

Task.WaitAll(tasksToAwait.ToArray());
}

公共异步任务CancelAsync()
{
cancelleTokenSource.Cancel();

试试
{
await Task.WhenAll(tasksToAwait.ToArray());
}
捕获(例如AggregateException)
{
ex.Handle(p => p是OperationCanceledException);
}
}
}
}


解决方案

.Net中的取消是协作的。



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



在您的 Task.Run 中,使用 CancellationToken 作为参数,但是您不必在任务本身中检查它,因此,只有在任务有机会启动之前发出信号令牌的情况下,任务才会被取消。 / p>

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

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






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

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


I created a small wrapper around CancellationToken and CancellationTokenSource. The problem I have is that the CancelAsync method of CancellationHelper doesn't work as expected.

I'm experiencing the problem with the ItShouldThrowAExceptionButStallsInstead method. To cancel the running task, it calls await coordinator.CancelAsync();, but the task is not cancelled actually and doesn't throw an exception on task.Wait

ItWorksWellAndThrowsException seems to be working well and it uses coordinator.Cancel, which is not an async method at all.

The question why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?

Don't let the waitHandle confuse you, it's only for not letting the task finish early.

Let the code speak for itself:

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);
            }
        }
    }
}

解决方案

Cancellation in .Net is cooperative.

That means that the one holding the CancellationTokenSource signals cancellation and the one holding the CancellationToken needs to check whether cancellation was signaled (either by polling the CancellationToken or by registering a delegate to run when it is signaled).

In your Task.Run you use the CancellationToken as a parameter, but you don't check it inside the task itself so the task will only be cancelled if the token was signaled before the task had to a chance to start.

To cancel the task while it's running you need to check the CancellationToken:

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


In your case you block on a ManualResetEvent so you wouldn't be able to check the CancellationToken. You can register a delegate to the CancellationToken that frees up the reset event:

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

这篇关于为什么在异步方法中调用CancellationTokenSource的Cancel方法时,任务没有被取消?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆