Task.WhenAny to Grab Only Task that resolves the Fastest - c#

I have two async methods that both return a List. They both search for records in a different way, and i need them both to run in parallel, and i only care about the task that finishes first. The other one should be canceled if possible.
I presume i should be using Task.WhenAny but don't seem to be getting the implementation correct. The below GoAsync implementation doesn't return any records, it jumps out of the routine (no errors) at await Task.WhenAny(taskList) line. I get no results, etc. How can i get the results? Am i going about this correctly?
public static async void GoAsync(SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
{
var taskList = new List<Task>();
taskList.Add(new Task(async () => await SearchRoutine1Async(search, cancellationToken)));
taskList.Add(new Task(async () => await SearchRoutine2Async( search, cancellationToken)));
Task completedTask = await Task.WhenAny(taskList);
}
The Async routines are something like this:
public static async Task<List<SearchModel>> SearchRoutine1Async( SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
{
using (DBContext db = new DBContext)
{
var searchModels= await db.SearchModel
.Where(sm => sm.subKey1 = search.subKey1)
.ToListAsync(cancellationToken)
;
return searchModels;
}
}
When i've tried running these tasks individually such as doing this: It works fine.
var callTask = Task.Run(() => SearchRoutine1Async(search));
callTask.Wait();
var list1 = callTask.Result;
var callTask2 = Task.Run(() => SearchRoutine2Async(search));
callTask2.Wait();
var list2 = callTask.Result;
[UPDATE]:
I've changed my GoAsync to this: according to one of the answers below: (but it's still not working)
public static async Task<List<SearchModel>> GoAsync(SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
{
var taskList = new List<Task<List<SearchModel>>>
{
SearchRoutine1Async(search, cancellationToken),
SearchRoutine2Async(search, cancellationToken)
};
Task<List<SearchModel>> completedTask = await Task.WhenAny(taskList);
return completedTask.Result;
}
I call the routine from a SYNCHRONOUS routine as such :
var result = GoAsync(search);
Note i'm deep in a routine that where i need to run these task in parallel in order to get a competing result. That routine as a signature of
private static void DoVariousCalucualtions() {}

You don't need to create new Task, asynchronous method will return a task.
public static async Task GoAsync(
SearchContract search,
CancellationToken cancellationToken = default(CancellationToken))
{
var taskList = new List<Task<List<SearchModel>>>
{
SearchRoutine1Async(search, cancellationToken),
SearchRoutine2Async( search, cancellationToken)
};
Task<List<SearchModel>> completedTask = await Task.WhenAny(taskList);
// use competedTask.Result here (safe as you know task is completed)
}
After question update:
To get results you need await for the function to complete.
var result = await GoAsync();
But you mentioned that this call located deep in the synchronous function with void signature and you are not using ASP.Net Core. That means you need to make whole "methods pipeline" asynchronous starting from controller action.
If you will use GoAsync().Result; you will end up with a deadlock, behaviour you already experienced...

Related

Use IAsyncEnumerable to replace Task and Action delegate

I should implement a method that can return a list of devices.
The goal is to return a device as soon as one is found without waiting for the search to finish.
To do this I think I use IAsyncEnumerable.
The Discover method of my implementation has an Action that takes care of adding an item to the list as soon as one is found.
How could I modify the Action and make sure that the new device is not added to a list but returned via "yield return"?
Below is the code I used:
public async IAsyncEnumerable<Device> ScanAsync(
CancellationToken cancellationToken = default(CancellationToken))
{
var devices = new List<DiscoveryDevice>();
await Discover(TimeoutSeconds, d => devices.Add(d), cancellationToken);
yield return new Device("", ""); //ERROR: return always last
}
private async Task Discover(int timeout, Action<DiscoveryDevice> onDeviceDiscovered,
CancellationToken cancellationToken = default(CancellationToken))
{
IEnumerable<IOnvifUdpClient> foreachInterface = clientFactory
.CreateClientForeachInterface();
if (!foreachInterface.Any())
throw new Exception(
"Missing valid NetworkInterfaces, UdpClients could not be created");
await Task.WhenAll(foreachInterface.Select(
client => Discover(timeout, client, onDeviceDiscovered,
cancellationToken)).ToArray());
}
private async Task Discover(int timeout, IOnvifUdpClient client,
Action<DiscoveryDevice> onDeviceDiscovered,
CancellationToken cancellationToken = default(CancellationToken))
{
var messageId = Guid.NewGuid();
var responses = new List<UdpReceiveResult>();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout));
try
{
await SendProbe(client, messageId);
while (true)
{
if (cts.IsCancellationRequested ||
cancellationToken.IsCancellationRequested)
break;
try
{
var response = await client.ReceiveAsync()
.WithCancellation(cancellationToken)
.WithCancellation(cts.Token);
if (IsAlreadyDiscovered(response, responses)) continue;
responses.Add(response);
var discoveredDevice = ProcessResponse(response, messageId);
if (discoveredDevice != null)
{
Task.Run(() => onDeviceDiscovered(discoveredDevice));
}
}
catch (Exception)
{ }
}
}
finally
{
client.Close();
}
}
You need a buffer to store the discovered devices, because the work of discovering the devices is not driven by the enumeration of the resulting IAsyncEnumerable<Device> sequence. In other words you have a push system, hidden behind a pull interface. When this happens you need a buffer, and a suitable buffer for your case is the Channel<T> class. You can find a synopsis of the features of this class here. Usage example:
public IAsyncEnumerable<Device> ScanAsync(
CancellationToken cancellationToken = default)
{
Channel<Device> devices = Channel.CreateUnbounded<Device>();
Task task = Discover(TimeoutSeconds,
dd => devices.Writer.TryWrite(new Device(dd.X, dd.Y)),
cancellationToken);
_ = task.ContinueWith(t => devices.Writer.TryComplete(t.Exception), default,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
return devices.Reader.ReadAllAsync(cancellationToken);
}
There are a couple of rough corners in the above code:
There is a fire-and-forget ContinueWith continuation.
The cancellationToken does not cancel gracefully. The resulting sequence might
be canceled before the completion of the Discover asynchronous method. That's a second case of fire-and-forget.
It might be possible to improve the code by ditching the Action<DiscoveryDevice> parameter, and passing instead the devices.Writer as argument (a parameter of type ChannelWriter<Device>).

chaining c# async / await tasks, recreating them as they complete

Aesthetic question really.
Given this code (polling unavoidable):
protected override async Task ExecuteAsync(CancellationToken ct)
{
// move the loop here somehow?
await Task.WhenAll(
Task.Run(async () => await this.PollA(ct), ct),
Task.Run(async () => await this.PollB(ct), ct),
Task.Run(async () => await this.PollC(ct), ct))
.ConfigureAwait(false);
}
the polling methods look like this at the moment, each one has a different delay.
private async Task Poll(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(Math.Max(1000, CONFIGA), ct);
this._logger.StartAction("poll A status");
this._logger.StopAction("poll A status");
}
}
Is there a way to structure a continuation that removes the loop in each of the Poll methods
private async Task Poll(CancellationToken ct)
{
await Task.Delay(Math.Max(1000, CONFIGA), ct);
this._logger.StartAction("poll A status");
this._logger.StopAction("poll A status");
}
This might not even be the right pattern, but it seems better than having three infinite loops.
Task.WhenAny([A,B,C]) =>
// recreate any complete task as soon as it returns
// and await the new "continuation"?
I have an Aesthetic solution, that is probably not advisable to be used since it will probably cause stack overflow eventually.
It maybe demonstrates why the loop is a better option.
I must admit do not really understand your example in a real world context.
In my mind almost all code that executes for a long time will do it in a finite loop, and thus to check for cancellation after each loop iteration sounds like a good idea to me.
Unless you want your code just to run infinitely until the task is canceled, in which case my aesthetic solution will probably cause a stack overflow if left to long, but it was fun none the less coming up with this code.
I created a Extension method:
public static class Extensions
{
public static async Task ContinueWithInfinitly(this Task task, Func<Task> continuationAction, CancellationToken cancellationToken)
{
await task;
if (!cancellationToken.IsCancellationRequested)
{
var newTask = continuationAction.Invoke();
await newTask.ContinueWithInfinitly(continuationAction, cancellationToken);
}
}
}
Which Base on your code will then be called as follows:
await Task.WhenAll(
Task.Run(async () => await this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct)),
Task.Run(async () => await this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct)),
Task.Run(async () => await this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct)))
.ConfigureAwait(false);
Although I dont see the point of wrapping each method again in a Task.Run.
So i can also just be
await Task.WhenAll(
this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct),
this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct),
this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct))
.ConfigureAwait(false);
You can use Task.WhenAny like this:
private async Task<Tuple<string, int>> Poll(string type, int delay, CancellationToken ct) {
await Task.Delay(Math.Max(1000, delay), ct);
Console.WriteLine($"poll {type} status");
// return input arguments back
return Tuple.Create(type, delay);
}
private async Task PollAll(CancellationToken ct) {
var tasks = new []
{
Poll("A", 3000, ct),
Poll("B", 2000, ct),
Poll("C", 1000, ct)
};
while (!ct.IsCancellationRequested) {
var completed = await Task.WhenAny(tasks);
var index = Array.IndexOf(tasks, completed);
// await to throw exceptions if any
await completed;
// replace with new task with the same arguments
tasks[index] = Poll(completed.Result.Item1, completed.Result.Item2, ct);
}
}

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

Task ContinueWith does not return expected value

This is probably one of those RTFM type questions, however, I cannot for the life of me figure out how chain tasks together in a way that works for Asp.net WebApi.
Specifically, I am looking how to use a DelegatingHandler to modify the response after it has been handled by the controller (add an additional header), and I am trying to unit test the DelegatingHandler by using a HttpMessageInvoker instance.
First Pattern
[TestMethod]
public void FirstTest()
{
var task = new Task<string>(() => "Foo");
task.ContinueWith(t => "Bar");
task.Start();
Assert.AreEqual("Bar", task.Result);
}
This fails on the assert because task.Result returns "Foo"
Second Pattern
[TestMethod]
public void SecondTest()
{
var task = new Task<string>(() => "Foo");
var continueTask = task.ContinueWith(t => "Bar");
continueTask.Start();
Assert.AreEqual("Bar", continueTask.Result);
}
This fails on continueTask.Start() with the exception of System.InvalidOperationException: Start may not be called on a continuation task.
Third Pattern
[TestMethod]
public void ThirdTest()
{
var task = new Task<string>(() => "Foo");
var continueTask = task.ContinueWith(t => "Bar");
task.Start();
Assert.AreEqual("Bar", continueTask.Result);
}
This test works the way I expect, however, I am not sure how to get this pattern to work with WebAPI.
Current Implementation
public class BasicAuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var task = base.SendAsync(request, cancellationToken);
task.ContinueWith(AddWwwAuthenticateHeaderTask());
return task;
}
private static Func<Task<HttpResponseMessage>, HttpResponseMessage>
AddWwwAuthenticateHeaderTask()
{
return task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=\"api\""));
}
return response;
};
}
}
However, when I invoke BasicAuthenticationHandler from a unit test, my header is not added before the Assert happens (if I debug, I notice that the header is added after the unit test fails).
[TestMethod]
public void should_give_WWWAuthenticate_header_if_authentication_is_missing()
{
using (var sut = new BasicAuthenticationHandler())
{
sut.InnerHandler = new DelegatingHttpMessageHandler(
() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
using (var invoker = new HttpMessageInvoker(sut))
{
var task = invoker.SendAsync(_requestMessage, CancellationToken.None);
task.Start();
Assert.IsTrue(
task.Result.Headers.WwwAuthenticate.Contains(
new AuthenticationHeaderValue("Basic", "realm=\"api\"")));
}
}
}
If I change my production code to return the continuation task instead of the result from base.SendAsync then I get the 2nd unit test exception about calling Start on a continuation task.
I think I want to accomplish the third unit test pattern in my production code, however, I have no idea on how to write that.
How do I do what I want (add the header before the assert gets called)?
When returning a Task, it should always be a Hot Task, meaning that the returned task has already been started. Making someone explicitly call Start() on a returned task is confusing and violates the guidelines.
To properly see the continuations result, do this:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(AddWwwAuthenticateHeaderTask()).Unwrap();
}
And you should modify base.SendAsync to return the already started Task
From the Task Asynchronous Pattern Guidelines:
All tasks returned from TAP methods must be “hot.” If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method. Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class).
I cannot for the life of me figure out how chain tasks together in a way that works for Asp.net WebApi.
Embrace async and await. In particular, replace ContinueWith with await (and do not use the task constructor or Start):
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=\"api\""));
}
return response;
}
Try the following. Note task2.Unwrap(), I think this part hasn't been addressed by the other answers:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var task1 = base.SendAsync(request, cancellationToken);
var task2 = task1.ContinueWith(t => AddWwwAuthenticateHeaderTask(),
cancellationToken);
return task2.Unwrap();
}
You need to unwrap the inner task because the type of task2 is Task<Task<HttpResponseMessage>>. This should provide correct continuation semantic and result propagation.
Check Stephen Toub's "Processing Sequences of Asynchronous Operations with Tasks". This complexity can be avoided with async/await.
If you can't use async/await, you still can further improve this code a bit, to avoid redundant thread switching otherwise caused by ContinueWith:
var task2 = task1.ContinueWith(
t => AddWwwAuthenticateHeaderTask(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
In either case, the continuation will not happen on the original synchronization context (which probably is AspNetSynhronizationContext). If you need to stay on the same context, use TaskScheduler.FromCurrentSynchronizationContext() instead of TaskScheduler.Default. A word of caution: this may cause a deadlock in ASP.NET.
ContinueWith returns the mapped task so you need to return that:
var task = base.SendAsync(request, cancellationToken);
return task.ContinueWith(AddWwwAuthenticateHeaderTask());

How to create a cancellable task loop?

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

Categories

Resources