Cancelling one of the concurrently executed methods - c#

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.

Related

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

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.

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;

What is the use of Cancellation token here

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.

Cancellation of multiple tasks in TPL based on return resutl

I have a scenario in which i have multiple functions to call in parallel and I am using TPL for that task. I have used
ConcurrentDictionary<string, List<result>> dictionary = new ConcurrentDictionary<string, List<result>>();
var tasks = Task.Run(()=>
{
new Task(()=> {dictionary.TryAdd("First", CallFirstFunction());});
new Task(()=> {dictionary.TryAdd("Second", CallSecondFunction());});
new Task(()=> {dictionary.TryAdd("Third", CallThirdFunction());});
new Task(()=> {dictionary.TryAdd("Fourth", CallFourthFunction());});
});
Now, i need to wait till all the function are executed and return some result in concurrent dictionary so i can used it for further process, But i also want to cancel any task if some returned result is empty regardless of task order. If any function returns the empty result i need to cancel all remaining tasks at the same time. I have checked the "CancellationToken" class as well but i am confused to use this with in the tasks and how to share the function states with in the tasks.
Any help will be appreciated.
Best Regards
To support cancellation you need to work with CancellationTokenSource. See https://msdn.microsoft.com/en-us/library/mt674886.aspx
What you need to do is to pass the CancellationTokenSource to your methods so when the result is empty you can call CancellationTokenSource.Cancel()
async Task DoWorkAsync()
{
var cts = new CancellationTokenSource();
var ct = cts.Token;
var tasks = new List<Task>
{
Task.Run(() => AddToDictionary("First", () => CallFirstFunction(), cts), ct),
Task.Run(() => AddToDictionary("Second", () => CallSecondFunction(), cts), ct),
Task.Run(() => AddToDictionary("Third", () => CallThirdFunction(), cts), ct),
Task.Run(() => AddToDictionary("Fourth", () => CallFourthFunction(), cts), ct),
};
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException ex)
{
}
}
private void AddToDictionary(string key, Func<List<int>> method, CancellationTokenSource cts)
{
if(cts.IsCancellationRequested)
return;
var result = method.Invoke();
if(!result.Any());
cts.Cancel();
if(!cts.IsCancellationRequested)
dictionary.TryAdd(key, result);
}
This example assumes that the operations you call (CallFirstFunction etc.) are not async (don't return a Task or Task). Otherwise you should pass the CancellationToken to them as well and check for cancellation using ct.ThrowIfCancellationRequested
Cancelling a Task generates an OperationCanceledException you need to handle.
Of course, when any method is already completed before another one returns an empty result, those results will already be present in the dictionary. If you need other behaviour this example will give you some basic idea about how the CancellationTokenSource relates to Tasks and CancellationTokens

Categories

Resources