我创建了一个围绕 CancellationToken
和 CancellationTokenSource
的小封装。我遇到的问题是,CancellationHelper
的 CancelAsync
方法并不能按照预期工作。
我在 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()
? - Matthew WatsonTask.Run
,令牌仅在任务构建期间相关,在运行时,您必须自己检查令牌... - user57508CancellationTokenSource.Cancel
,那么它的task.Result
应该是TaskStatus.Canceled
,不是吗? - Balázs Szántótask.Status
就不会是TaskStatus.Canceled
,即使调用了Cancel
,我仍然需要检查CancellationTokenSource.IsCancellationRequested
。 - Balázs Szántótoken.ThrowIfCancellationRequested()
,就有可能出现TaskStatus.Faulted
的情况。 - user57508