问题:有没有办法将
CancellationToken
与从 Task
方法返回的 async
相关联?
通常,如果
Task
与与 Canceled
的 OperationCancelledException
匹配的 CancellationToken
一起抛出,Task
最终会处于 CancellationToken
状态。如果它们不匹配,则任务进入 Faulted
状态:
void WrongCancellationTokenCausesFault()
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
cts2.Cancel();
// This task will end up in the Faulted state due to the task's CancellationToken
// not matching the thrown OperationCanceledException's token.
var task = Task.Run(() => cts2.Token.ThrowIfCancellationRequested(), cts1.Token);
}
对于
async
/await
,我还没有找到一种方法来设置方法的Task
的CancellationToken
(从而实现相同的功能)。从我的测试来看,似乎 any OperationCancelledException
会导致 async
方法进入 Canceled 状态:
async Task AsyncMethodWithCancellation(CancellationToken ct)
{
// If ct is cancelled, this will cause the returned Task to be in the Cancelled state
ct.ThrowIfCancellationRequested();
await Task.Delay(1);
// This will cause the returned Task to be in the Cancelled state
var newCts = new CancellationTokenSource();
newCts.Cancel();
newCts.Token.ThrowIfCancellationRequested();
}
如果能有更多的控制权就好了,因为如果我从
async
方法调用的方法被取消(而且我不希望取消——即它不是这个 Task
的 CancellationToken
),我希望任务进入 Faulted
状态,而不是 Canceled
状态。
我认为该设计非常适合常见情况:如果取消任何子操作,则取消会传播到父级(最常见的情况是父级和子级共享取消令牌)。
如果您想要不同的语义,您可以在
catch
方法中 OperationCanceledException
async
并抛出符合您需要的语义的异常。如果您想重复使用这些语义,Task
的扩展方法应该符合要求。
这是一个
Run
方法,当提供 Task.Run
参数时,它尝试模仿 CancellationToken
方法的行为。如果异步方法返回 Run
任务,并且关联的 Canceled
与提供的参数匹配,则
Canceled
返回的任务只能变为 CancellationToken
。
/// <summary>
/// Invokes an async method (a method implemented with the async keyword), and
/// returns a proxy of the produced async task. In case the async task completes
/// in a Canceled state but the causative CancellationToken is not equal with the
/// cancellationToken argument, the proxy transitions to a Faulted state.
/// In all other cases, the proxy propagates the status of the async task as is.
/// </summary>
public static Task<TResult> Run<TResult>(Func<Task<TResult>> asyncMethod,
CancellationToken cancellationToken)
{
return asyncMethod().ContinueWith(t =>
{
if (t.IsCanceled)
{
// In case the async method is canceled with an unknown token, throw
// an exception. The continuation will complete in a Faulted state.
CancellationToken taskToken = new TaskCanceledException(t).CancellationToken;
if (taskToken != cancellationToken)
return Task.FromException<TResult>(new OperationCanceledException(taskToken));
}
return t; // In any other case, propagate the task as is.
}, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
.Unwrap();
}
使用示例:
Task<int> task = Run(async () =>
{
await Task.Delay(1000, new CancellationToken(true));
return 13;
}, new CancellationToken(false));
try { task.Wait(); } catch { }
Console.WriteLine(task.Status);
输出:
Faulted