我正在编写一个具有ValueTask<T>
返回类型并接受CancellationToken
. 如果CancellationToken
在调用该方法时已经取消了,我想返回一个取消的ValueTask<T>
( ),它在等待时IsCanceled == true
传播一个。OperationCanceledException
使用异步方法执行此操作很简单:
async ValueTask<int> MyMethod1(CancellationToken token)
{
token.ThrowIfCancellationRequested();
//...
return 13;
}
ValueTask<int> task = MyMethod1(new CancellationToken(true));
Console.WriteLine($"IsCanceled: {task.IsCanceled}"); // True
await task; // throws OperationCanceledException
我决定切换到非异步实现,但现在我无法重现相同的行为。Task.FromCanceled
将结果正确包装到 cancelled ValueTask<T>
,但异常的类型是TaskCanceledException
,这是不可取的:
ValueTask<int> MyMethod2(CancellationToken token)
{
if (token.IsCancellationRequested)
return new ValueTask<int>(Task.FromCanceled<int>(token));
//...
return new ValueTask<int>(13);
}
ValueTask<int> task = MyMethod2(new CancellationToken(true));
Console.WriteLine($"IsCanceled: {task.IsCanceled}"); // True
await task; // throws TaskCanceledException (undesirable)
另一个不成功的尝试是包装一个Task.FromException
. 这个传播了正确的异常类型,但任务是错误的而不是取消的:
ValueTask<int> MyMethod3(CancellationToken token)
{
if (token.IsCancellationRequested)
return new ValueTask<int>(
Task.FromException<int>(new OperationCanceledException(token)));
//...
return new ValueTask<int>(13);
}
ValueTask<int> task = MyMethod3(new CancellationToken(true));
Console.WriteLine($"IsCanceled: {task.IsCanceled}"); // False (undesirable)
await task; // throws OperationCanceledException
这个问题有什么解决方案,还是我应该接受我的 API 行为不一致,有时会传播TaskCanceledException
s(当令牌已被取消时),有时会传播OperationCanceledException
s(当令牌稍后被取消时)?
更新:作为我试图避免的不一致的一个实际示例,这是内置Channel<T>
类中的一个:
Channel<int> channel = Channel.CreateUnbounded<int>();
ValueTask<int> task1 = channel.Reader.ReadAsync(new CancellationToken(true));
await task1; // throws TaskCanceledException
ValueTask<int> task2 = channel.Reader.ReadAsync(new CancellationTokenSource(100).Token);
await task2; // throws OperationCanceledException
第一个ValueTask<int>
抛出 a TaskCanceledException
,因为令牌已经被取消。第二个ValueTask<int>
抛出一个OperationCanceledException
,因为令牌在 100 毫秒后被取消。