Question about task continuation - c#

task1:
public Task SendMail(string email)
{
...
}
task2:
public Task<int> SaveToDB(User user)
{
...
}
What I need to do:
when task1 fail, should return a "send failure" infomation;
when task1 success, task2 begin.
when task2 fail, should return a "save failure" infomation.
when task2 success, should return a "save success" infomation.
Please help me find a solution:
public Task<string> SendAndSave(User user){
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
return "Send Failed";
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
return "Save Succeeded";
}
catch(AggregateException ae)
{
return "Save Failed";
}
},
TaskContinuationOptions.NotOnFaulted);
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result);
}
When I run it I receive an error.
I invoked it as:
var result = SendAndSave(user).Result;
the error occured at:
public Task<string> SendAndSave(User user)
{
...
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result); //Here: A task was cancelled
}
After debugging it , I have two questions:
Q1: after task1, task1Failed, task2 are created, the value of each
CreationOption property is "none" although its Status is
"WaitingForActivation". It seems all the continuation options are
invalid.
Q2: For the task of Task.Factory.ContinueWhenAny(new[] {task1Failed,
task2}, t => t.Result), let's name it "factoryTask", the
ContinueWhenAny seems invalid. After put a breakpoint in the internal
of each of task1Failed, task2 and factoryTask, I can see occasionally
the breakpoint in the factoryTask was hitted firstly, though it
should be hitted after either task1Failed or task2 had completed.
Anybody can help ? Thanks.

Although I don't fully understand your questions, I understand the issue here, basically you will always have either task1Failed or task2 completes but not both, and the other one will be cancelled!
So in WaitOnAny call, if it finds one of the tasks is already cancelled, it will crash, and this really depends on the order of the tasks parameter, so in your example above you pass task1failed first, so if this task completeted ContinueWhenAny will work fine because the first task it checks is already completed so it doesn't check the other one, but if task1Failed didn't run so it is in a cancelled state, ContinueWhenAny will throw!
To avoid this, you need to use TaskCompletetionSource instead, and the task which runs, will set the value, then at the end you return tcs.Task.Result
public Task<string> SendAndSave(User user){
var tcs = new TaskCompletionSource<string>();
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
tcs.TrySetResult("Send Failed");
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
tcs.TrySetResult("Save Succeeded");
}
catch(AggregateException ae)
{
tcs.TrySetResult("Save Failed");
}
},TaskContinuationOptions.NotOnFaulted);
return tcs.Task.Result;
}

Related

Skip the await task response conditional

I have the following code:
//Await #1
var response1 = await doSomething();
if(response1.isSuccess) {
//Await #2
var response2 = await doSomethingElse();
}
Response 1 and response 2 are totally independent and i want to parallelize the await task here.
Basically response 2 takes a lot of time and hence is only invoked when response1 is success.
Is there any way in which i can invoke both tasks and see the result of response 1 and if it is fail, i drop/skip the response of Await#2.
Essentially what you want is to cancel a task, but with a little more logic.
You need to edit doSomethingElse so that it accepts a CancellationToken, and also so that it makes use of it to stop what its doing:
public async Task<Foo> DoSomethingElse(CancellationToken token) {
...
if (token.IsCancellationRequested) {
// stop what you are doing...
// I can't tell you how to implement this without seeing how DoSomethingElse is implemented
}
...
}
Now, get a CancellationToken from a CancellationTokenSource:
var source = new CancellationTokenSource();
var token = source.Token;
And here comes the logic of "if response 1 fails cancel response 2":
var response2Task = DoSomethingElse(token);
var response1 = await DoSomething();
if (!response1.IsSuccess) {
source.Cancel();
} else {
var response2 = await response2Task;
}
var task2 = doSomethingElse();
var response1 = await doSomething();
if(response1.isSuccess) {
var response2 = await task2;
}
This will start the execution of doSomethingElse() immediately, and only wait for its completion when response1.isSuccess == true
You can launch both threads, then once the first task obtains a result, you could either stop, or 'wait' for the second thread to finish.
The conditional logic, imo, should be placed in the first task's thread. If you do not have access to the doSomething, make a lambda in which you will await doSomething's response and then proceed with the condition logic.
How to stop a thread? Here you go, and here you go
This is what you can do:
var task1 = Task.Run(() =>
{
Console.WriteLine("running task 1");
return 1;
});
var task2 = Task.Run(() =>
{
Console.WriteLine("running task 2");
return 2;
});
await Task.WhenAll(task1, task2);
So you will run the 2 tasks in parallel. If you need to check the result of particular tasks, this is the way to do that(it won't trigger the task again if you run Task.WhenAll() previously. It will just get you the results of previously executed tasks):
var result1 = await task1;
var result2 = await task2;
Then you can apply some condition:
if(result1 == 1) return result2;
etc..

Task Behaviour in a process

My code does not have a problem actually but its a case of performance issue which I may run into.
According to what I know about tasks, a task which starts first say TaskA can be running parallel with another task say TaskB but TaskB may finish and exit its thread before TaskA completes its work. So in my code, I'm trying to proceed with an operation but first knwow the task status. Is it true that this task called TaskResult in my code will complete before the preceeding lines of codes will run or should I use a Task.Wait() on it if that's what I want?
public virtual async Task<OperationResult> DeleteAsync(Expression<Func<TEntity, bool>> expression)
{
Task TaskResult = await _DbContext.Set<TEntity>().RemoveRange(IEnumerable<TEntity>);
if (TaskResult.Status == TaskStatus.RanToCompletion)
{
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.Deleted,
Succeeded = true
};
}
if (TaskResult.Status == TaskStatus.Faulted)
{
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.UnknownError,
Succeeded = false
};
}
}
The await keyword will stop execution of your method until the Task is completed. Once complete, await will either return the result of the Task, or throw an exception that happened inside the Task (if there was one). So there is no point testing to see if a Task faulted if you already awaited it, because if it faulted, await would have thrown an exception.
Just wrap it in a try/catch block.
But your code won't even compile because:
DbSet<T>.RemoveRange doesn't return a Task because it doesn't actually talk to the database at all. It just removes the records in memory. You need to call DbContext.SaveChangesAsync() for your changes to be committed.
You're not actually passing a list of objects to RemoveRange. (Did you change that code for this example?)
Once you await a Task, the await keyword returns the result of that Task, not the Task itself.
Here's an example of how you could write that:
public virtual async Task<OperationResult> DeleteAsync(Expression<Func<TEntity, bool>> expression) {
_DbContext.Set<TEntity>().RemoveRange(somethinghere);
try {
//commit the changes
var result = await _DbContext.SaveChangesAsync();
//result will be an int (the number of records changed), not a Task
//if we got this far, we succeeded
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.Deleted,
Succeeded = true
};
} catch (Exception e) {
return new OperationResult()
{
Message = "", //you can use e.Message here if you want
ReturnObject = null,
Status = OperationStatus.UnknownError,
Succeeded = false
};
}
}
Note that you could just put:
await _DbContext.SaveChangesAsync();
If you don't intend on using result.

Task not in a faulted state when an exception is thrown

I can't seem to figure out why my task is not in a faulted state when an exception is thrown in the following sample (assume engine.Send(...) throws an exception):
var receiverTask = new Task<Task>(async () => {
result = await Task.FromResult(engine.Send(someObject));
});
receiverTask.Start();
receiverTask.Wait();
if I do receiverTask.IsFaulted after the receiverTask.Wait() it returns false instead of true as I would expect.
I have been reading that I need to use something like the following construct:
receiverTask.ContinueWith(t => { /* error handling */ },
TaskContinuationOptions.OnlyOnFaulted);
But I can't seem to figure out how to integrate this into the sample code. Do I call this before the Start()? before the Wait()?
I would like the calling program to somehow be aware that the receiverTask has thrown an exception and failed. Any pointers?
If I correctly understand this context, you could leave out the .Start() and use .Wait() somewhat differently.
(I don't know what your engine.Send(someObject) method is, so I have to assume.)
Let's say your method returns a bool (it doesn't matter, but just to write something down.
If it is synchronous, the returned Task could be:
Task<bool> receiverTask = Task<bool>.Run(async () =>
await Task<bool>.FromResult(engine.Send(someObject)));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await Task<bool>.FromResult(engine.Send(someObject));
return _result;
}, TaskScheduler.Default).Unwrap();
If it is asynchronous, the returned Task could be:
Task<bool> receiverTask = Task.Run(async () =>
await engine.Send(someObject));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await engine.Send(someObject);
return _result;
}, TaskScheduler.Current).Unwrap<bool>();
(However, all these are blocking if there's no other async/await declaration).
Once your Task is scheduled, some Continuation condition can be defined to evaluate its status.
If the Task is in some faulty status (canceled, aborted etc.) its .IsCompleted property reports true, but the .Status property is not TaskStatus.RanToCompletion.
receiverTask.ContinueWith(t =>
{
//Continue on faulted
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted).Wait(0);
receiverTask.ContinueWith(t =>
{
//Continue on canceled
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsCanceled)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnCanceled).Wait(0);
receiverTask.ContinueWith(t =>
{
//Standard behaviour
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
Console.WriteLine(receiverTask.Status.ToString());
}, TaskContinuationOptions.None).Wait();
//This writes only if no errors have been raised
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
But you might also use a Try/Catch block:
try
{
receiverTask.Wait();
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
}
catch (Exception)
{
receiverTask.ContinueWith(t =>
{
//With continuation
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted);
//or without
//if (receiverTask.IsCanceled)
//Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}

Catching exception throwing by tasks

TaskScheduler ts= TaskScheduler.FromCurrentSynchronizationContext();
try
{
Task<T> task1 = ...
task1.ContinueWith(t =>
{
...
Task<T> task2 = ...
task2.ContinueWith(u =>
{
...
Task<T> task3 = ...
task3.ContinueWith(w =>
{
...
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}
catch(Exception)
{
MessageBox.Show("...");
}
Hi. I have some code (as above). This doesn't work for me. I have three tasks which are working server-side but modifying UI, so all of them should result make in UI thread. What is more: 3rd task cannot run if 2nd complete with failure, and 2nd cannot run unless 1st is successfully completed. So if 1st one ends with a failure my tasks tree should throw exception and break rest of operations. How to achieve that in the simplest way?
UPDATE
now my code looks like
private async void SomeMethod()
{
...
try
{
var r1 = await Method1(...);
var r2 = await Method2(...);
var r3 = await Method3(...);
}
catch
{
MessageBox.Show("...");
}
}
private Task<...> Method1(...)
{
Task<...> task = Task<...>.Factory.StartNew(() =>
{
...
try
{
// Result is null (but that's ok) so 'index out of range exception' is thrown
// It calls my method MyException with this exception (but I don't know in
// which thread and how to catch this (shouldn't be catch by async SomeMethod?)
result = ....Results[0];
}
catch (Exception ex)
{
MyException(ex);
}
return result;
});
return task;
}
public void MyException(Exception ex)
{
throw ex;
}
But I still cannot catch exception.
EDIT
Solved. I don't catch exceptions (just ignore in Method1) and:
var r1 = await Method1(...);
if(r1!=null)
{
var r2 = await Method2(...);
var r3 = await Method3(...);
}
else
{
...do sth instead of catching ex
}
The easiest option is to use await here, as it will provide the error handling semantics you want with very little effort; allowing you to write code as if it were regular synchronous code:
try
{
var firstResult = await SomethingAsync();
var secondResult = await SomethingElseAsync(firstResult);
var finalResult = await AnotherThingAsync(secondResult);
}
catch
{
//handle an exception thrown by any of the above async operations.
}
If you can't do that (due to being on .NET 4.0), then you can use the Then method described here to also get the semantics you want:
var finalTask = SomethingAsync()
.Then(firstResult => SomethingElseAsync(firstResult))
.Then(secondResult => AnotherThingAsync(secondResult));
finalTask.ContinueWith(t => HandleError(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
Then is essentially a call to ContinueWith, but with different error handling semantics. If the tasks being continued threw an exception then Then just propagates that exception, rather than running the continuation. ContinueWith just runs the continuation and swallows the exception.

Asynchronous Task.WhenAll with timeout

Is there a way in the new async dotnet 4.5 library to set a timeout on the Task.WhenAll method? I want to fetch several sources, and stop after say 5 seconds, and skip the sources that weren't finished.
You could combine the resulting Task with a Task.Delay() using Task.WhenAny():
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));
If you want to harvest completed tasks in case of a timeout:
var completedResults =
tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
I think a clearer, more robust option that also does exception handling right would be to use Task.WhenAny on each task together with a timeout task, go through all the completed tasks and filter out the timeout ones, and use await Task.WhenAll() instead of Task.Result to gather all the results.
Here's a complete working solution:
static async Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult));
var completedTasks =
(await Task.WhenAll(tasks.Select(task => Task.WhenAny(task, timeoutTask)))).
Where(task => task != timeoutTask);
return await Task.WhenAll(completedTasks);
}
Check out the "Early Bailout" and "Task.Delay" sections from Microsoft's Consuming the Task-based Asynchronous Pattern.
Early bailout. An operation represented by t1 can be grouped in a
WhenAny with another task t2, and we can wait on the WhenAny task. t2
could represent a timeout, or cancellation, or some other signal that
will cause the WhenAny task to complete prior to t1 completing.
What you describe seems like a very common demand however I could not find anywhere an example of this. And I searched a lot... I finally created the following:
TimeSpan timeout = TimeSpan.FromSeconds(5.0);
Task<Task>[] tasksOfTasks =
{
Task.WhenAny(SomeTaskAsync("a"), Task.Delay(timeout)),
Task.WhenAny(SomeTaskAsync("b"), Task.Delay(timeout)),
Task.WhenAny(SomeTaskAsync("c"), Task.Delay(timeout))
};
Task[] completedTasks = await Task.WhenAll(tasksOfTasks);
List<MyResult> = completedTasks.OfType<Task<MyResult>>().Select(task => task.Result).ToList();
I assume here a method SomeTaskAsync that returns Task<MyResult>.
From the members of completedTasks, only tasks of type MyResult are our own tasks that managed to beat the clock. Task.Delay returns a different type.
This requires some compromise on typing, but still works beautifully and quite simple.
(The array can of course be built dynamically using a query + ToArray).
Note that this implementation does not require SomeTaskAsync to receive a cancellation token.
In addition to timeout, I also check the cancellation which is useful if you are building a web app.
public static async Task WhenAll(
IEnumerable<Task> tasks,
int millisecondsTimeOut,
CancellationToken cancellationToken)
{
using(Task timeoutTask = Task.Delay(millisecondsTimeOut))
using(Task cancellationMonitorTask = Task.Delay(-1, cancellationToken))
{
Task completedTask = await Task.WhenAny(
Task.WhenAll(tasks),
timeoutTask,
cancellationMonitorTask
);
if (completedTask == timeoutTask)
{
throw new TimeoutException();
}
if (completedTask == cancellationMonitorTask)
{
throw new OperationCanceledException();
}
await completedTask;
}
}
Check out a custom task combinator proposed in http://tutorials.csharp-online.net/Task_Combinators
async static Task<TResult> WithTimeout<TResult>
(this Task<TResult> task, TimeSpan timeout)
{
Task winner = await (Task.WhenAny
(task, Task.Delay (timeout)));
if (winner != task) throw new TimeoutException();
return await task; // Unwrap result/re-throw
}
I have not tried it yet.
void result version of #i3arnon 's answer, along with comments and changing first argument to use extension this.
I've also got a forwarding method specifying timeout as an int using TimeSpan.FromMilliseconds(millisecondsTimeout) to match other Task methods.
public static async Task WhenAll(this IEnumerable<Task> tasks, TimeSpan timeout)
{
// Create a timeout task.
var timeoutTask = Task.Delay(timeout);
// Get the completed tasks made up of...
var completedTasks =
(
// ...all tasks specified
await Task.WhenAll(tasks
// Now finish when its task has finished or the timeout task finishes
.Select(task => Task.WhenAny(task, timeoutTask)))
)
// ...but not the timeout task
.Where(task => task != timeoutTask);
// And wait for the internal WhenAll to complete.
await Task.WhenAll(completedTasks);
}
Seems like the Task.WaitAll overload with the timeout parameter is all you need - if it returns true, then you know they all completed - otherwise, you can filter on IsCompleted.
if (Task.WaitAll(tasks, myTimeout) == false)
{
tasks = tasks.Where(t => t.IsCompleted);
}
...
I came to the following piece of code that does what I needed:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;
using System.Json;
using System.Threading;
namespace MyAsync
{
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
Console.WriteLine("Start Main");
List<Task<List<MyObject>>> listoftasks = new List<Task<List<MyObject>>>();
listoftasks.Add(GetGoogle(cts));
listoftasks.Add(GetTwitter(cts));
listoftasks.Add(GetSleep(cts));
listoftasks.Add(GetxSleep(cts));
List<MyObject>[] arrayofanswers = Task.WhenAll(listoftasks).Result;
List<MyObject> answer = new List<MyObject>();
foreach (List<MyObject> answers in arrayofanswers)
{
answer.AddRange(answers);
}
foreach (MyObject o in answer)
{
Console.WriteLine("{0} - {1}", o.name, o.origin);
}
Console.WriteLine("Press <Enter>");
Console.ReadLine();
}
static async Task<List<MyObject>> GetGoogle(CancellationTokenSource cts)
{
try
{
Console.WriteLine("Start GetGoogle");
List<MyObject> l = new List<MyObject>();
var client = new HttpClient();
Task<HttpResponseMessage> awaitable = client.GetAsync("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=broersa", cts.Token);
HttpResponseMessage res = await awaitable;
Console.WriteLine("After GetGoogle GetAsync");
dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
Console.WriteLine("After GetGoogle ReadAsStringAsync");
foreach (var r in data.responseData.results)
{
l.Add(new MyObject() { name = r.titleNoFormatting, origin = "google" });
}
return l;
}
catch (TaskCanceledException)
{
return new List<MyObject>();
}
}
static async Task<List<MyObject>> GetTwitter(CancellationTokenSource cts)
{
try
{
Console.WriteLine("Start GetTwitter");
List<MyObject> l = new List<MyObject>();
var client = new HttpClient();
Task<HttpResponseMessage> awaitable = client.GetAsync("http://search.twitter.com/search.json?q=broersa&rpp=5&include_entities=true&result_type=mixed",cts.Token);
HttpResponseMessage res = await awaitable;
Console.WriteLine("After GetTwitter GetAsync");
dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
Console.WriteLine("After GetTwitter ReadAsStringAsync");
foreach (var r in data.results)
{
l.Add(new MyObject() { name = r.text, origin = "twitter" });
}
return l;
}
catch (TaskCanceledException)
{
return new List<MyObject>();
}
}
static async Task<List<MyObject>> GetSleep(CancellationTokenSource cts)
{
try
{
Console.WriteLine("Start GetSleep");
List<MyObject> l = new List<MyObject>();
await Task.Delay(5000,cts.Token);
l.Add(new MyObject() { name = "Slept well", origin = "sleep" });
return l;
}
catch (TaskCanceledException)
{
return new List<MyObject>();
}
}
static async Task<List<MyObject>> GetxSleep(CancellationTokenSource cts)
{
Console.WriteLine("Start GetxSleep");
List<MyObject> l = new List<MyObject>();
await Task.Delay(2000);
cts.Cancel();
l.Add(new MyObject() { name = "Slept short", origin = "xsleep" });
return l;
}
}
}
My explanation is in my blogpost:
http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html
In addition to svick's answer, the following works for me when I have to wait for a couple of tasks to complete but have to process something else while I'm waiting:
Task[] TasksToWaitFor = //Your tasks
TimeSpan Timeout = TimeSpan.FromSeconds( 30 );
while( true )
{
await Task.WhenAny( Task.WhenAll( TasksToWaitFor ), Task.Delay( Timeout ) );
if( TasksToWaitFor.All( a => a.IsCompleted ) )
break;
//Do something else here
}
You can use the following code:
var timeoutTime = 10;
var tasksResult = await Task.WhenAll(
listOfTasks.Select(x => Task.WhenAny(
x, Task.Delay(TimeSpan.FromMinutes(timeoutTime)))
)
);
var succeededtasksResponses = tasksResult
.OfType<Task<MyResult>>()
.Select(task => task.Result);
if (succeededtasksResponses.Count() != listOfTasks.Count())
{
// Not all tasks were completed
// Throw error or do whatever you want
}
//You can use the succeededtasksResponses that contains the list of successful responses
How it works:
You need to put in the timeoutTime variable the limit of time for all tasks to be completed. So basically all tasks will wait in maximum the time that you set in timeoutTime. When all the tasks return the result, the timeout will not occur and the tasksResult will be set.
After that we are only getting the completed tasks. The tasks that were not completed will have no results.
I tried to improve on the excellent i3arnon's solution, in order to fix some minor issues, but I ended up with a completely different implementation. The two issues that I tried to solve are:
In case more than one of the tasks fail, propagate the errors of all failed tasks, and not just the error of the first failed task in the list.
Prevent memory leaks in case all tasks complete much faster than the timeout.
Leaking an active Task.Delay might result in a non-negligible amount of leaked memory, in case the WhenAll is called in a loop, and the timeout is large.
On top of that I added a cancellationToken argument, XML documentation that explains what this method is doing, and argument validation. Here it is:
/// <summary>
/// Returns a task that will complete when all of the tasks have completed,
/// or when the timeout has elapsed, or when the token is canceled, whatever
/// comes first. In case the tasks complete first, the task contains the
/// results/exceptions of all the tasks. In case the timeout elapsed first,
/// the task contains the results/exceptions of the completed tasks only.
/// In case the token is canceled first, the task is canceled. To determine
/// whether a timeout has occured, compare the number of the results with
/// the number of the tasks.
/// </summary>
public static Task<TResult[]> WhenAll<TResult>(
Task<TResult>[] tasks,
TimeSpan timeout, CancellationToken cancellationToken = default)
{
if (tasks == null) throw new ArgumentNullException(nameof(tasks));
tasks = tasks.ToArray(); // Defensive copy
if (tasks.Any(t => t == null)) throw new ArgumentException(
$"The {nameof(tasks)} argument included a null value.", nameof(tasks));
if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
throw new ArgumentOutOfRangeException(nameof(timeout));
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled<TResult[]>(cancellationToken);
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(timeout);
var continuationOptions = TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously;
var continuations = tasks.Select(task => task.ContinueWith(_ => { },
cts.Token, continuationOptions, TaskScheduler.Default));
return Task.WhenAll(continuations).ContinueWith(allContinuations =>
{
cts.Dispose();
if (allContinuations.IsCompletedSuccessfully)
return Task.WhenAll(tasks); // No timeout or cancellation occurred
Debug.Assert(allContinuations.IsCanceled);
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled<TResult[]>(cancellationToken);
// Now we know that timeout has occurred
return Task.WhenAll(tasks.Where(task => task.IsCompleted));
}, default, continuationOptions, TaskScheduler.Default).Unwrap();
}
This WhenAll implementation elides async and await, which is not advisable in general. In this case it is necessary, in order to propagate all the errors in a not nested AggregateException. The intention is to simulate the behavior of the built-in Task.WhenAll method as accurately as possible.
Usage example:
string[] results;
Task<string[]> whenAllTask = WhenAll(tasks, TimeSpan.FromSeconds(15));
try
{
results = await whenAllTask;
}
catch when (whenAllTask.IsFaulted) // It might also be canceled
{
// Log all errors
foreach (var innerEx in whenAllTask.Exception.InnerExceptions)
{
_logger.LogError(innerEx, innerEx.Message);
}
throw; // Propagate the error of the first failed task
}
if (results.Length < tasks.Length) throw new TimeoutException();
return results;
Note: the above API has a design flaw. In case at least one of the tasks has failed or has been canceled, there is no way to determine whether a timeout has occurred. The Exception.InnerExceptions property of the task returned by the WhenAll may contain the exceptions of all tasks, or part of the tasks, and there is no way to say which is which. Unfortunately I can't think of a solution to this problem.

Categories