I have this small test in .NET 6
, which works fine (it's green).
Func<CancellationToken, Task> taskFactory = async token => throw new OperationCanceledException();
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
However, the compiler (rightfully?) complains: warning CS1998: This async method lacks 'await' operators and will run synchronously
. I could not figure out a way to transform this into a synchronous variant.
I tried these 2 options for the lambda
- without async:
token => throw new OperationCanceledException()
. It's pretty clear to me that this will just throw the exception directly on the stack instead of wrapping it in the task, but this is what the IDE suggested. token => Task.FromException(new OperationCanceledException())
. This goes toIsFaulted
instead ofIsCanceled
.
What is the correct way to transfrom this to a synchronous variant?
CodePudding user response:
You could await a completed task:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.CompletedTask;
throw new OperationCanceledException();
};
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
However your test does have a race condition, although it's likely to usually succeed. But if you change it to this:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.Delay(1000);
throw new OperationCanceledException();
};
var task = taskFactory(CancellationToken.None);
Console.WriteLine(task.IsCanceled);
it will print false
. To fix that, you have to wait for the task to complete:
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);
Note that I'm using Task.WaitAny()
to wait for the task to complete without throwing a TaskCanceledException
. If you simply await task;
it will of course throw that exception.
CodePudding user response:
You could use the Task.FromCanceled
method:
Func<CancellationToken, Task> taskFactory =
token => Task.FromCanceled(new CancellationToken(true));
CodePudding user response:
You could use the low-level APIs as well to achieve the desired outcome in a synchronous way.
TaskCompletionSource tcs = new();
Func<CancellationToken, Task> taskFactory = token => tcs.Task;
With this approach your delegate remains sync (don't need to use the async
keyword)
CancellationTokenSource cts = new(0);
cts.Token.Register(() => tcs.TrySetCanceled());
Here we connect the Task and Cancellation primitives via a simple callback.
We cancel the Task right away.
And that's it :) A working example can be found here.