我在.NET 6
中进行了这个小测试,它工作得很好(它是绿色的)。
Func<CancellationToken, Task> taskFactory = async token => throw new OperationCanceledException();
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
然而,编译器(正确地?)报错:warning CS1998: This async method lacks 'await' operators and will run synchronously
。我无法找到将其转换为同步变体的方法。我为lambda
- without async:
token => throw new OperationCanceledException()
。这对我来说很清楚,这将只是抛出异常直接在堆栈上,而不是在任务中包装它,但这是IDE的建议。 token => Task.FromException(new OperationCanceledException())
。这是IsFaulted
,而不是IsCanceled
。
将此转换为同步变量的正确方法是什么?
编辑:这段代码的目的是测试一些CUT是否正确处理来自taskFactory的OperationCanceledException为emitted
。因此,异常仍然需要在解决方案中发出(可能是抛出)。我不是在简单地寻找一种方法来设置任务取消。
您可以等待一个已完成的任务:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.CompletedTask;
throw new OperationCanceledException();
};
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
然而,您的测试确实有一个竞争条件,尽管它通常很可能成功。但是如果你把它改成这样:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.Delay(1000);
throw new OperationCanceledException();
};
var task = taskFactory(CancellationToken.None);
Console.WriteLine(task.IsCanceled);
它将打印false
。要解决这个问题,您必须等待任务完成:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.Delay(1000);
throw new OperationCanceledException();
};
var task = taskFactory(CancellationToken.None);
Task.WaitAny(task);
Console.WriteLine(task.IsCanceled);
请注意,我使用Task.WaitAny()
等待任务完成,而没有抛出TaskCanceledException
。如果你只执行await task;
,它当然会抛出那个异常。
您也可以使用低级api以同步的方式实现期望的结果。
TaskCompletionSource tcs = new();
Func<CancellationToken, Task> taskFactory = token => tcs.Task;
使用这种方法,您的委托保持同步(不需要使用async
关键字)
CancellationTokenSource cts = new(0);
cts.Token.Register(() => tcs.TrySetCanceled());
这里我们通过一个简单的回调连接Task和cancel原语。
我们马上取消任务。
就是这样:)一个工作的例子可以在这里找到。