Cancel Task and set status to "Cancelled" - c#

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 I​Async​Info 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;
}

Related

Task.WaitAsync vs Task.Wait [duplicate]

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);
}
}

Task does not complete if another task with in it gets cancelled

In this code, the task t will never complete (never outputs to the console) if I cancel the token, even though the token is not used for t and only used for a task inside t
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Task t = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000, token);
Console.WriteLine("Task Completed");
});
source.Cancel();
How can I cancel the token but also let the task complete
Notes:
I tried using Task.Run instead, and got the same output
t.IsCompleted will stay false indefinitely (task will never complete)
The delay task does complete
You can make the t to survive the cancellation, by handling and suppressing the OperationCanceledException that is thrown by the Task.Delay:
Task t = Task.Run(async () =>
{
try { await Task.Delay(1000, token); } catch (OperationCanceledException) { }
Console.WriteLine("Task Completed");
});

Set timeout for user defined function using cancelation token

I want to cancel a Task if it lasts longer than 3 seconds.
First attempt:
public static async Task DoSomething()
{
await Task.Delay(10000);
}
var task1 = DoSomething();
var task2 = Task.Delay(3000);
Task.WaitAny(task1, task2);
Second attempt:
I tried to use cancellationToken but it does not seems to work. It waits for the function 10 seconds and it seems it ignores the 3 seconds delayed cancellation.
var cts = new CancellationTokenSource();
var token = cts.Token;
cts.CancelAfter(TimeSpan.FromSeconds(3));
await Task.Run(async () => await DoSomething(), token);
Can someone help me with implementing such functionality using cancellationToken?
You need to pass CancellationToken to DoSomething and use it there:
public static async Task DoSomething(CancellationToken t)
{
await Task.Delay(10000, t);
}
var cts = new CancellationTokenSource();
var token = cts.Token;
cts.CancelAfter(TimeSpan.FromSeconds(3));
try
{
await Task.Run(async () => await DoSomething(token), token);
}
catch (OperationCanceledException ex)
{
// canceled
}
From docs:
When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state. If you do not use a Wait or WaitAll method to wait for the task, then the task just sets its status to Canceled.
But it seems Task transitions to Canceled independent of if the same token is provided or not.
The only difference I could find between passing token to Task.Run or not was that if cancellation is requested before the task begins execution, the task does not execute, instead it is set to the Canceled state and throws a TaskCanceledException exception.

Async/Await equivalent to .ContinueWith with CancellationToken and TaskScheduler.FromCurrentSynchronizationContext() scheduler

This is a follow-up to this question.
Question: What would be a succinct way to express the following using async/await instead of .ContinueWith()?:
var task = Task.Run(() => LongRunningAndMightThrow());
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task updateUITask = task.ContinueWith(t => UpdateUI(t), ct, TaskContinuationOptions.None, uiTaskScheduler);
I'm mainly interested in the case of a UI SynchronizationContext (e.g. for Winforms)
Note with this that the behavior has all the following desired behaviors:
When the CancellationToken is cancelled, the updateUITask ends up cancelled as soon as possible (i.e. the LongRunningAndMightThrow work may still be going on for quite some time).
The ct CancellationToken gets checked for cancellation on the UI thread prior to running the UpdateUI lambda (see this answer).
The updateUITask will end up cancelled in some cases where the task completed or faulted (since the ct CancellationToken is checked on the UI thread before executing the UpdateUI lambda.
There is no break in flow between the check of the CancellationToken on the UI thread and the running of the UpdateUI lambda. That is, if the CancellationTokenSource is only ever cancelled on the UI thread, then there is no race condition between the checking of the CancellationToken and the running of the UpdateUI lambda--nothing could trigger the CancellationToken in between those two events because the UI thread is not relinquished in between those two events.
Discussion:
One of my main goals in moving this to async/await is to get the UpdateUI work out of a lambda (for ease of readability/debuggability).
#1 above can be addressed by Stephen Toub's WithCancellation task extension method. (which you can feel free to use in the answers).
The other requirements seemed difficult to encapsulate into a helper method without passing UpdateUI as a lambda since I cannot have a break (i.e. an await) between the checking of the CancellationToken and the executing of UpdateUI (because I assume I cannot rely on the implementation detail that await uses ExecuteSynchronously as mentioned here. This is where it seems that having the mythical Task extension method .ConfigureAwait(CancellationToken) that Stephen talks about would be very useful.
I've posted the best answer I have right now, but I'm hoping that someone will come up with something better.
Sample Winforms Application demonstrating the usage:
public partial class Form1 : Form
{
CancellationTokenSource m_cts = new CancellationTokenSource();
private void Form1_Load(object sender, EventArgs e)
{
cancelBtn.Enabled = false;
}
private void cancelBtn_Click(object sender, EventArgs e)
{
m_cts.Cancel();
cancelBtn.Enabled = false;
doWorkBtn.Enabled = true;
}
private Task DoWorkAsync()
{
cancelBtn.Enabled = true;
doWorkBtn.Enabled = false;
var task = Task.Run(() => LongRunningAndMightThrow());
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task updateUITask = task.ContinueWith(t => UpdateUI(t), ct, TaskContinuationOptions.None, uiTaskScheduler);
return updateUITask;
}
private async void doWorkBtn_Click(object sender, EventArgs e)
{
try
{
await DoWorkAsync();
MessageBox.Show("Completed");
}
catch (OperationCanceledException)
{
MessageBox.Show("Cancelled");
}
catch
{
MessageBox.Show("Faulted");
}
}
private void UpdateUI(Task<bool> t)
{
// We *only* get here when the cancel button was *not* clicked.
cancelBtn.Enabled = false;
doWorkBtn.Enabled = true;
// Update the UI based on the results of the task (completed/failed)
// ...
}
private bool LongRunningAndMightThrow()
{
// Might throw, might complete
// ...
return true;
}
}
Stephen Toub's WithCancellation extension method:
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;
}
Related Links:
Equivalent of ContinueWith(delegate, CancellationToken) with await continuation
http://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx
https://stackoverflow.com/a/15673072/495262
https://stackoverflow.com/a/17560746/495262
Writing a WithCancellation method can be done much simpler, in just one line of code:
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);
}
As for the operation you want to do, just using await instead of ContinueWith is just as easy as it sounds; you replace the ContinueWith with an await. Most of the little pieces can be cleaned up a lot though.
m_cts.Cancel();
m_cts = new CancellationTokenSource();
var result = await Task.Run(() => LongRunningAndMightThrow())
.WithCancellation(m_cts.Token);
UpdateUI(result);
The changes are not huge, but they're there. You [probably] want to cancel the previous operation when starting a new one. If that requirement doesn't exist, remove the corresponding line. The cancellation logic is all already handled by WithCancellation, there is no need to throw explicitly if cancellation is requested, as that will already happen. There's no real need to store the task, or the cancellation token, as local variables. UpdateUI shouldn't accept a Task<bool>, it should just accept a boolean. The value should be unwrapped from the task before callingUpdateUI.
The following should be equivalent:
var task = Task.Run(() => LongRunningAndMightThrow());
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
try
{
await task.WithCancellation(ct);
}
finally
{
ct.ThrowIfCancellationRequested();
UpdateUI(task);
}
Notice that the try/finally is required for the case where the LongRunningAndMightThrow method faults, but by the time we return to the UI thread the CancellationToken has been triggered. Without it the returned outer Task would be faulted where in the original ContinueWith case, it would have been cancelled.

How to transform task.Wait(CancellationToken) to an await statement?

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.

Categories

Resources