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

编程入门 行业动态 更新时间:2024-10-24 11:11:53
本文介绍了为什么在异步方法中调用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方法时,任务没有被取消?

本文发布于:2023-11-08 06:59:25,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1568664.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:方法   CancellationTokenSource   Cancel

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!