Set timeout for user defined function using cancelation token - c#

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.

Related

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

GetContextAsync() with Cancellation Support

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

How to get the result of a task when using Task.WhenAny to account for a timeout

I need to add a timeout feature to the task calls in a mobile app. I attempted to complete this by using Task.WhenAny as shown below. This returns the task that finishes first. My problem is that originally I was getting the return value from this task, and I still need it if the task does not get timed out.
Task task = restService.GetRequestAsync(articleAdr, articleParams);
var response = await Task.WhenAny(task, Task.Delay(1000, cts.Token));
response is simply the task that was completed first. How do I get it's result?
I can think of three different possibilities for this scenario.
The first two can be found in Peter Bons' answer.
The third is storing off your two tasks then checking the status after the await Task.WhenAny() is completed.
var workerTask = restService.GetRequestAsync(articleAdr, articleParams);
var cancellationTask = Task.Delay(1000, cts.Token);
await Task.WhenAny(workerTask, cancellationTask);
if (workerTask.Status == TaskStatus.RanToCompletion)
{
// Note that this is NOT a blocking call because the Task ran to completion.
var response = workerTask.Result;
// Do whatever work with the completed result.
}
else
{
// Handle the cancellation.
// NOTE: You do NOT want to call workerTask.Result here. It will be a blocking call and will block until
// your previous method completes, especially since you aren't passing the CancellationToken.
}
One question though, how is your CancellationTokenSource created and initialized and when do you call Cancel on it?
Best would be if your method GetRequestAsync would accept a CancellationToken. Always prefer that if possible since you can create a CancellationTokenSource that initiates a cancel after a set period. Would save you the call to Task.WhenAny.
In general, there are multiple options, one is outlined below:
// Set timeout of 1 second
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(1));
...
Task task = restService.GetRequestAsync(articleAdr, articleParams);
// Wait until timeout or request is done.
await Task.WhenAny(task, Task.Delay(TimeSpan.FromMilliseconds(-1), cts.Token));
// If the cancellation is not yet requested the request was done before the timeout was reached
if(!cts.IsCancellationRequested)
{
var response = await task;
}
Another options is this:
Task requestTask = restService.GetRequestAsync(articleAdr, articleParams);
var firstCompletedTask = await Task.WhenAny(requestTask, Task.Delay(1000, cts.Token));
if(firstCompletedTask == requestTask)
{
cts.Cancel(); // optionally, will cancel the delay task since it is of no use any more.
var response = await requestTask;
}
A completed task can be awaited as many times as you want and it will always yield the same result.
I think you can take a look to #jamesmontemagno MVVM Helpers. There is an extesion that helps you to add a timeout to a task
MVVM Helpers - Utils
Here you can find a video where James explain how to use it
The-Xamarin-Show-12-MVVM-Helpers
(near 26:38 minutes)
I need to add a timeout feature to the task calls in a mobile app. I attempted to complete this by using Task.WhenAny as shown below.
First, you should be aware that by not passing the CancellationToken to GetRequestAsync, you're not actually cancelling the request. It will continue processing.
Second, I find your code rather odd, since there are two timeouts possible in its current state: the Task.Delay may complete, or the CancellationToken may be signaled. One of these (Task.Delay) is a normal task completion, and the other (CancellationToken) is a true cancellation.
If the CancellationToken is your timeout, then you can use the WaitAsync method from my Nito.AsyncEx.Tasks library:
Task task = restService.GetRequestAsync(articleAdr, articleParams);
await task.WaitAsync(cts.Token);
var result = await task;
If the CancellationToken is a user-requested cancellation, and that the Task.Delay is the timeout you want to apply, then I'd recommend modeling your timeout as another kind of cancellation:
Task task = restService.GetRequestAsync(articleAdr, articleParams);
using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token))
{
timeoutCts.CancelAfter(1000);
await task.WaitAsync(timeoutCts.Token);
}
var result = await task;
If you don't wish to use Nito.AsyncEx.Tasks, then your best option is probably something like this (assuming Task.Delay is intended as a timeout and CancellationToken is a user cancellation request):
Task task = restService.GetRequestAsync(articleAdr, articleParams);
var completed = await Task.WhenAny(task, Task.Delay(1000, cts.Token));
if (completed != task)
throw new OperationCanceledException();
var result = await task;

Why is this Task not Finishing before my code passes the Wait Command

I have a task which runs a loop and delays for an interval each iteration. Once the CancellationTokenSource calls Cancel() I want my main code to Wait() for the Task.Delay(interval) to finish and the task to exit the loop before my code continues. I thought this code would work but it doesn't.
Instead my main code passes the t.Wait() before the Loop exits. Why?
Main Method Code:
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
var t = Task.Run(() => { MyLoopTask(200, ct); });
// Prepare information
cts.Cancel();
t.Wait();
// Send Information
Task Code
private async Task MyLoopTask(int interval, CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested)
{
Debug.Print(" Still In Loop ");
// Do something
await Task.Delay(interval);
}
Debug.Print(" cancelled ");
//Clean up
}
You create a task with Task.Run that fires and forgets the actual task you get back from MyLoopTask.
Task.Run is redundant here. You can just call MyLoopTask and use the task it returns.
var t = MyLoopTask(200, ct);
// ...
t.Wait();
If you still have some reason to use Task.Run you can do so by making sure the delegate waits for the actual task by awaiting it:
var t = Task.Run(async () => await MyLoopTask(200, ct));
// ...
t.Wait();

Cancelling one of the concurrently executed methods

I am using Task.WaitAny to call 3 different methods (TrySolution1, TrySolution2 and TrySolution3) concurrently. My requirement is to find which method gets executed first and abort/cancel the other methods execution, If the first method returns result.
Tried using CancellationTokenSource to perform cancellation of the other tasks once the first method executes, but could see that the other methods still executes.
My code snippet:
Task<Boolean>[] tasks = new Task<Boolean>[3];
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
tasks[0] = Task<Boolean>.Factory.StartNew(() => TrySolution1());
tasks[1] = Task<Boolean>.Factory.StartNew(() => TrySolution2());
tasks[2] = Task<Boolean>.Factory.StartNew(() => TrySolution3());
Task.WaitAny(tasks, ct);
cts.Cancel();
You need to set the cancellation token to cancelled at the end of each attempt and then check for it in each method to see if it was cancelled at some point. Something like this...check out Task Cancellation
Be careful though. This will set the cancellation task, so although one task will complete the token will still be set.
var tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
var task[0] = Task.Factory.StartNew(() =>
{
// Were we already canceled?
ct.ThrowIfCancellationRequested();
bool moreToDo = true;
while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
tokenSource.Cancel();
}, tokenSource.Token); // Pass same token to StartNew.
There is no forced aborting of code that's already executing in the TPL. What you need to do is to pass the CancellationToken into all of your TrySolutionN methods and then check it at appropriate points when executing them or pass it to methods you're calling from them.
To check whether the passed-in token is canceled in TrySolutionN, use ThrowIfCancellationRequested() or IsCancellationRequested.

Categories

Resources