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;
}
Related
So I'm spinning up a HttpListener to wait for an OAuth2 response. In an ideal world, this is only going to be alive for a few seconds while the user logs in in the browser and we get posted the token.
I'd also like for this to have a CancellationToken so that the user can stop listening after a delay should they so wish.
My initial idea was to use something along the lines of:
_listener.Start();
Task<HttpListenerContext> t = _listener.GetContextAsync();
while (!cancelled.IsCancellationRequested)
{
if (t.IsCompleted)
{
break;
}
await Task.Run(() => Thread.Sleep(100));
}
HttpListenerContext ctx = t.Result;
//...
_listener.Stop();
But that doesn't sit right with me for so many reasons (weird async usage, polling, etc.).
So then I thought I might be able to use the synchronous version _listener.GetContext() in conjunction with Task.Run(func<T>, CancellationToken):
_listener.Start()
HttpListenerContext ctx = await Task.Run(() => _listener.GetContext(), cancelled);
//...
_listener.Stop();
This is a little better, the code's at least tidier, although it seems hacky using a synchronous version of the method asynchronously with a Task...
However this doesn't behave how I'd expect (aborting the running task when the token is cancelled).
This strikes me as something that really ought to be fairly simple to do so I assume I'm missing something.
So my question is thus... How do I listen asynchronously with a HttpListener in a cancellable fashion?
Because the GetContextAsync method does not support cancellation, it basically means that it is unlikely you can cancel the actual IO operation, yet unlikely to cancel the Task returned by the method, until you Abort or Stop the HttpListener. So the main focus here is always a hack that returns the control flow to your code.
While both the answers from #guru-stron and #peter-csala should do the trick, I just wanted to share another way without having to use Task.WhenAny.
You could wrap the task with a TaskCompletionSource like this:
public static class TaskExtensions
{
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
{
if (!token.CanBeCanceled)
{
return task;
}
var tcs = new TaskCompletionSource<T>();
// This cancels the returned task:
// 1. If the token has been canceled, it cancels the TCS straightaway
// 2. Otherwise, it attempts to cancel the TCS whenever
// the token indicates cancelled
token.Register(() => tcs.TrySetCanceled(token),
useSynchronizationContext: false);
task.ContinueWith(t =>
{
// Complete the TCS per task status
// If the TCS has been cancelled, this continuation does nothing
if (task.IsCanceled)
{
tcs.TrySetCanceled();
}
else if (task.IsFaulted)
{
tcs.TrySetException(t.Exception);
}
else
{
tcs.TrySetResult(t.Result);
}
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
}
And flow the control like this:
var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
try
{
var context = await listener.GetContextAsync().AsCancellable(cts.Token);
}
catch (TaskCanceledException)
{
// ...
}
I would suggest creating cancelable infinite task (Task.Delay(Timeout.Infinite, token) for example) and use Task.WhenAny. Something like that:
var cts = new CancellationTokenSource(); // token source controled by consumer "outside"
var token = cts.Token;
var httpListener = new HttpListener();
httpListener.Start();
var t = httpListener.GetContextAsync();
// to cancel the infinite delay task if listener finishes first
var localCts = CancellationTokenSource.CreateLinkedTokenSource(token);
var completed = await Task.WhenAny(t, Task.Delay(Timeout.Infinite, localCts.Token));
if (completed == t) // check that completed task is one from listener
{
localCts.Cancel(); // cancel the infinite task
HttpListenerContext ctx = t.Result;
//...
}
httpListener.Stop();
Here is yet another solution:
var cancellationSignal = new TaskCompletionSource<object>();
var contextTask = _listener.GetContextAsync();
using (cancelled.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), cancellationSignal))
{
if (contextTask != await Task.WhenAny(contextTask, cancellationSignal.Task).ConfigureAwait(false))
break; //task is cancelled
}
Because we can't await the CancellationToken that's why have to apply the following trick
The CancellationToken does expose a Register method, where we can define a callback which will be called whenever the cancellation occurs
Here we can provide a delegate which sets an awaitable to completed
So, we can await that task
In order to create a Task which is set to completed whenever the cancellation occurs I've used TaskCompletionSource. You could also use SemaphoreSlim or any other signalling object which has async wait, like AsyncManualResetEvent.
So, we pass the cancellationSignal to the Register as a state parameter
Inside the delegate we have to cast it back to TCS to be able to call the TrySetResult on it
Inside the using block we await a Task.WhenAny
It will return that Task which finishes first
If that Task is the cancellation then we can break / return / throw ...
If that Task is the contextTask then we can continue the normal flow
I've the following chunk of code :
using (var cancelSource = new CancellationTokenSource())
{
Task[] tasks = null;
var cancelToken = cancelSource.Token;
tasks = new[]
{
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000)) //<---
};
await Task.Delay(howLongSecs * 1000); // <---
cancelSource.Cancel();
await Task.WhenAll(tasks);
}
Where ThrowAfterAsync have this :
private async Task ThrowAfterAsync(string taskId, CancellationToken cancelToken, int afterMs)
{
await Task.Delay(afterMs, cancelToken);
var msg = $"{taskId} throwing after {afterMs}ms";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
Resharper is suggesting me that I can use the overload of Task.Run() with cancellation token like this :
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000), cancelToken)
But why ? What is the benefit of doing this over the first version, without the cancellation token as parameter ?
In this specific case, there is no point. In general, you'd want to do as it suggests because, by passing the token to Task.Run it can avoid even scheduling the operation in the first place if the token is cancelled before the operation even has a chance to start, but in your case you're creating the token and you know it won't be cancelled when you start the operation.
But the reason you don't need to pass the token to Task.Run is because the code starting that task is the operation responsible for cancelling the token, and so it knows that the token isn't cancelled yet. Normally you'd be accepting a token from elsewhere, and you wouldn't know if/when it was cancelled.
All that said, there's no reason to even use Task.Run at all. You can just write:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
It will have the same behavior but without needlessly starting a new thread just to start the asynchronous operation.
Next, your code will never return in less than howLongSecs seconds, even if the operation finishes before then, because of how you've structured your code. You should simply provide the timeout to the cancellation token source and let it take care of canceling the token at the right time, it won't delay the rest of your method if the operation finishes before the cancellation should happen, so your whole method can just be written as:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
Resharper sees that you are using a method (Task.Run) which has overload which accepts CancellationToken, you have instance of CancellationToken in scope, but you do not use that overload which accepts a token. It does not perform any extensive analysys of your code - it's as simple as that. You can easily verify this with this code:
class Program {
static void Main() {
CancellationToken ct;
Test("msg"); // will suggest to pass CancellationToken here
}
private static void Test(string msg) {
}
private static void Test(string msg, CancellationToken ct) {
}
}
Yes the code itself is strange and you don't need to wrap your async in Task.Run at all, but I won't touch that since you asked just why Resharper suggests that.
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.
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.
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.