So, task.Wait() can be transformed to await task. The semantics are different, of course, but this is roughly how I would go about transforming a blocking code with Waits to an asynchronous code with awaits.
My question is how to transform task.Wait(CancellationToken) to the respective await statement?
await is used for asynchronous methods/delegates, which either accept a CancellationToken and so you should pass one when you call it (i.e. await Task.Delay(1000, cancellationToken)), or they don't and they can't really be canceled (e.g. waiting for an I/O result).
What you can do however, is abandon* these kinds of tasks with this extension method:
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted // fast-path optimization
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
Usage:
await task.WithCancellation(cancellationToken);
* The abandoned task doesn't get cancelled, but your code behaves as though it has. It either ends with a result/exception or it will stay alive forever.
To create a new Task that represents an existing task but with an additional cancellation token is quite straightforward. You only need to call ContinueWith on the task, use the new token, and propagate the result/exceptions in the body of the continuation.
public static Task WithCancellation(this Task task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
public static Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
This allows you to write task.WithCancellation(cancellationToken) to add a token to a task, which you can then await.
From https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#cancelling-uncancellable-operations
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
// This disposes the registration as soon as one of the tasks trigger
using (cancellationToken.Register(state =>
{
((TaskCompletionSource<object>)state).TrySetResult(null);
},
tcs))
{
var resultTask = await Task.WhenAny(task, tcs.Task);
if (resultTask == tcs.Task)
{
// Operation cancelled
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
}
Here is another solution:
Task task;
CancellationToken token;
await Task.WhenAny(task, Task.Delay(Timeout.Infinite, token));
See this answer that talks about using Task.Delay() to create a Task from a CancellationToken. Here are the docs for Task.WhenAny and Task.Delay.
Related
This question already has answers here:
await vs Task.Wait - Deadlock?
(3 answers)
Closed 6 months ago.
I know that Task.Wait() block thread in which it is executed.
Do I understand correctly that Task.WaitAsync() does not do this?
I tried to find information about it but I didn't find anything
WaitAsnync will return a new task that needs to be awaited in turn. It's not used to avoid await, it's used to allow cancelling a wait for another task.
If you want you await for a task to complete without blocking you'll have to use await in an async method, or use ContinueWith with the continuation code:
async Task MyMethodAsync(Task myTask)
{
...
await myTask;
...
}
This code can await forever and doesn't allow cancelling the wait. If you want to stop waiting after a while you can use Task.WaitAsync
...
try
{
await myTask.WaitAsync(TimeSpan.FromMinute(1));
}
catch(TimeoutException)
{
//Handle the timeout
}
...
Or you may want to cancel awaiting that task if a parent call signals cancellation through a CancellationTokenSource
async Task MyMethod(Task someTask,CancellationToken cancellationToken=default)
{
....
await someTask.WaitAsync(cancellationToken);
...
}
It is non-blocking since it returns a Task. See the documentation.
public Task WaitAsync(CancellationToken cancellationToken);
public Task WaitAsync(TimeSpan timeout);
public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken);
Gets a Task that will complete when this Task completes, when the specified timeout expires, or when the specified CancellationToken has cancellation requested.
The implementation can be found here:
public static Task WaitAsync(this Task task, int millisecondsTimeout) =>
WaitAsync(task, TimeSpan.FromMilliseconds(millisecondsTimeout), default);
public static Task WaitAsync(this Task task, TimeSpan timeout) =>
WaitAsync(task, timeout, default);
public static Task WaitAsync(this Task task, CancellationToken cancellationToken) =>
WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
public async static Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (new Timer(s => ((TaskCompletionSource<bool>)s).TrySetException(new TimeoutException()), tcs, timeout, Timeout.InfiniteTimeSpan))
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs))
{
await(await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)).ConfigureAwait(false);
}
}
I have code with looks something like:
I was trying to avoid putting it in a Task.Run because it is async and should run fine on the main thread
However it does not do a context switch (and will run forever), unless I insert a Task.Delay into the loop
Is there a better way of achieving this (Without Task.Run)?
var tasks = new List<Task>();
var cts = new CancellationTokenSource();
tasks.Add(DoSomething(cts.Token));
cts.Cancel();
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Done");
async Task DoSomething(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await Task.CompletedTask;
await Task.Delay(1); // Without this is doesn't do context switch
}
}
I'm trying to unit test cancellation when I have a heavy workload an encountered this problem.
Try
async Task DoSomething(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
//await Task.CompletedTask; //you can omit this line
await Task.Yield();
}
}
If you want to simulate a cancelable operation, the simplest way is probably this:
Task DoSomething(CancellationToken cancellationToken)
{
return Task.Delay(Timeout.Infinite, cancellationToken);
}
The cancellationToken parameter has canceling semantics, meaning that when the token is canceled, the Task will transition to the Canceled state. If you want it to have stopping semantics, which is the non standard semantics for a CancellationToken, you could do this:
async Task DoSomething(CancellationToken stoppingToken)
{
try { await Task.Delay(Timeout.Infinite, stoppingToken); }
catch (OperationCanceledException) { }
}
Now when the token is canceled, the Task will transition to the RanToCompletion state. Be aware that it's extremely rare to see a CancellationToken parameter with stopping semantics in the standard .NET libraries.
I want a task to finish when the 50 Milliseconds are over. The status of the task should then be set to "Cancelled", otherwise to "RunToCompletion".
The task creation is here:
CancellationTokenSource cts = new CancellationTokenSource(50);
CancellationToken ct = cts.Token;
Task test_task = Task.Run(async () =>
{
try
{
tokenS.Token.Register(() =>
{
cts.Cancel();
ct.ThrowIfCancellationRequested();
});
await NotifyDevice(BLEDevice);
}
catch (Exception e)
{
}
},ct);
All i get till now is an AggregateException, that does not get catched somehow by the try/catch-block.
Here is similar question to yours: Is it possible to cancel a C# Task without a CancellationToken?. But the solution will not cancel the task in NotifyDevice method. That task can be cancelled only if underlying task supports cancellation. And based on the docs IAsyncInfo can be cancelled. I would go with the wrapper to ensure the task is cancelled in 50ms in case if cancelling underlying task takes more time:
CancellationTokenSource cts = new CancellationTokenSource(50);
await NotifyDevice(BLEDevice, cts.Token).WithCancellation(cts.Token);
EDIT: the extension method itself:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using(cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
Is it possible to use System.Threading.Task.Task to create a loop of task that can be cancelled?
The flow should start with a Task.Delay(x ms) then continue with userdefined task, then another Task.Delay(y ms) and repeat from the user defined task.
var result = Task.Delay(initialDelay)
.ContinueWith(t => dostuff..)
.ContinueWith what goes here?
Is it even doable using tasks?
I could spin up a timer and be done with it, but using task seems to be the right way to go if I need cancellation, no?
await makes this super easy:
public async Task TimedLoop(Action action,
CancellationToken token, TimeSpan delay)
{
while (true)
{
token.ThrowIfCancellationRequested();
action();
await Task.Delay(delay, token);
}
}
Without async (but still just using the TPL) it's a bit messier. I generally solve this problem by having a continuation that attaches itself to a variable of type Task. This works fine, but it can take a second to wrap your head around it. Without await it may be easier to just use a Timer instead.
public Task TimedLoop(Action action,
CancellationToken token, TimeSpan delay)
{
//You can omit these two lines if you want the method to be void.
var tcs = new TaskCompletionSource<bool>();
token.Register(() => tcs.SetCanceled());
Task previous = Task.FromResult(true);
Action<Task> continuation = null;
continuation = t =>
{
previous = previous.ContinueWith(t2 => action(), token)
.ContinueWith(t2 => Task.Delay(delay, token), token)
.Unwrap()
.ContinueWith(t2 => previous.ContinueWith(continuation, token));
};
previous.ContinueWith(continuation, token);
return tcs.Task;
}
I have an async method that I want to be able to cancel that is currently called
string html = await Html.WebClientRetryAsync(state);
I have been trying to figure out the syntax to be able to call this method passing it a CancellationToken. Here is what I have attempted so far.
CancellationToken ct;
Func<object, Task<string>> func = async (s) => await WebClientRetryAsync((string)s);
Task<Task<string>> task = Task<Task<string>>.Factory.StartNew(func, state.Uri.AbsoluteUri, ct);
string html = await task.Result;
I plan to check inside the method to see if cancellation was requested prior to proceeding. I couldn't find any examples in the documentation
What I have won't get the token to the WebClientRetryAsync method, so this won't work.
If the underlying async method simply doesn't support cancellation in any way then there simply isn't any way for you to cancel it, and that's that, as is discussed in How do I cancel non-cancelable async operations?. You can have your code continue executing on after the cancellation token is cancelled, but if the underlying asynchronous operation has no way of being told it should stop, you have no way of stopping it.
If you do just want a way of moving on when the task finishes, assuming the timeout is reached, then you can use these extension methods to add that functionality:
public static Task AddCancellation(this Task task, CancellationToken token)
{
return Task.WhenAny(task, task.ContinueWith(t => { }, token))
.Unwrap();
}
public static Task<T> AddCancellation<T>(this Task<T> task,
CancellationToken token)
{
return Task.WhenAny(task, task.ContinueWith(t => t.Result, token))
.Unwrap();
}
Realize of course that if you await the result of a call like this and the task is cancelled, it will throw an exception, which is how you would support that path.