为什么在async方法中调用CancellationTokenSource的Cancel方法,任务没有被取消?
Why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?
我围绕 CancellationToken
和 CancellationTokenSource
创建了一个小包装。我遇到的问题是 CancellationHelper
的 CancelAsync
方法没有按预期工作。
我遇到了 ItShouldThrowAExceptionButStallsInstead
方法的问题。要取消 运行 任务,它会调用 await coordinator.CancelAsync();
,但实际上并没有取消任务,也不会在 task.Wait
上抛出异常
ItWorksWellAndThrowsException
似乎运行良好,它使用 coordinator.Cancel
,这根本不是异步方法。
为什么在async方法中调用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);
}
}
}
}
.NET 中的取消是合作的。
这意味着持有 CancellationTokenSource
的人发出取消信号,而持有 CancellationToken
的人需要检查是否发出了取消信号(通过轮询 CancellationToken
或注册一个收到信号时委托给 运行。
在您的 Task.Run
中,您使用 CancellationToken
作为参数,但您没有在任务本身内部检查它,因此只有在任务之前发出令牌时才会取消任务必须有机会开始。
要在 运行ning 期间取消任务,您需要检查 CancellationToken
:
var task = Task.Run(() =>
{
token.ThrowIfCancellationRequested();
}, token);
在您的情况下,您阻塞了 ManualResetEvent
,因此您将无法检查 CancellationToken
。您可以向释放重置事件的 CancellationToken
注册委托:
token.Register(() => waitHandle.Set())
我围绕 CancellationToken
和 CancellationTokenSource
创建了一个小包装。我遇到的问题是 CancellationHelper
的 CancelAsync
方法没有按预期工作。
我遇到了 ItShouldThrowAExceptionButStallsInstead
方法的问题。要取消 运行 任务,它会调用 await coordinator.CancelAsync();
,但实际上并没有取消任务,也不会在 task.Wait
ItWorksWellAndThrowsException
似乎运行良好,它使用 coordinator.Cancel
,这根本不是异步方法。
为什么在async方法中调用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);
}
}
}
}
.NET 中的取消是合作的。
这意味着持有 CancellationTokenSource
的人发出取消信号,而持有 CancellationToken
的人需要检查是否发出了取消信号(通过轮询 CancellationToken
或注册一个收到信号时委托给 运行。
在您的 Task.Run
中,您使用 CancellationToken
作为参数,但您没有在任务本身内部检查它,因此只有在任务之前发出令牌时才会取消任务必须有机会开始。
要在 运行ning 期间取消任务,您需要检查 CancellationToken
:
var task = Task.Run(() =>
{
token.ThrowIfCancellationRequested();
}, token);
在您的情况下,您阻塞了 ManualResetEvent
,因此您将无法检查 CancellationToken
。您可以向释放重置事件的 CancellationToken
注册委托:
token.Register(() => waitHandle.Set())