Parallel start of several tasks, each of which has its own timeout - c#

I have several similar tasks, each of which is limited to a timeout, i.e. must be completed faster than the specified time or return an empty result. The main purpose of these tasks is receiving a response from the server with a timeout limit.
An example of such task is below:
public async Task<List<Data>> GetDataWithTimeoutAsync(InputData data, int timeout)
{
List<Data> resultData = new List<Data>;
await Task.WhenAny(Task.Run(async () =>
{
resultData.Add(SomeWork(data));
}),
Task.Delay(timeout));
return resultData;
}
Each of these tasks works correctly separately.
But I want to run some of such tasks in parallel. For this, I use the following code.
public async Task<List<List<Data>>> GetAllDataAsync()
{
var resultTasks = new ConcurrentBag<Task<List<Data>>>();
var firtsTask = GetDataWithTimeoutAsync(firstInputData, firtsTimeout);
var secondTask = GetDataWithTimeoutAsync(secondInputData, secondTimeout);
var thirdTask = GetDataWithTimeoutAsync(thirdInputData, thirdTimeout);
resultTasks.Add(Task.Run(() => firtsTask));
resultTasks.Add(Task.Run(() => secondTask));
resultTasks.Add(Task.Run(() => thirdTask));
await Task.WhenAll(resultTasks);
var result = resultTasks.Select(t => t.Result).ToList();
return result;
}
But this code works incorrectly if different timeouts are set for nested tasks. In this case all of tasks are completed after the smallest of the timeouts.
How i can run some tasks in parallel with WhenAll if each of task is a result of WhenAny?

Your code doesn't compile, so I wrote something similar. I can't reproduce your results. In my case the WhenAll with different timeouts works as expected. It completes when the longest running task is completed, which is the second one (200 msec).
public static async Task Main(string[] args)
{
var task1 = GetDataAsync(100).WithTimeout(50); // Should timeout after 50 msec
var task2 = GetDataAsync(200).WithTimeout(300); // Should complete after 200 msec
var task3 = GetDataAsync(300).WithTimeout(100); // Should timeout after 100 msec
var stopwatch = Stopwatch.StartNew();
var results = await Task.WhenAll(task1, task2, task3); // Wait for all
stopwatch.Stop();
Console.WriteLine($"Results: {String.Join(", ", results)}");
Console.WriteLine($"Elapsed: {stopwatch.ElapsedMilliseconds} msec");
}
private static async Task<int> GetDataAsync(int input) // the input is used as delay
{
await Task.Delay(input);
return input;
}
public static Task<T> WithTimeout<T>(this Task<T> task, int timeout)
{
var delayTask = Task.Delay(timeout).ContinueWith(_ => default(T),
TaskContinuationOptions.ExecuteSynchronously);
return Task.WhenAny(task, delayTask).Unwrap();
}
Output:
Results: 0, 200, 0
Elapsed: 211 msec

Related

How to best run two async tasks in parallel and wait for the returned results of both? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
I'm looking for a simple C# example of a pattern that meets the following requirements:
I want to execute 2 methods asynchronously, initiating the second without waiting for the first to finish.
Each method accepts a parameter and returns and output value.
I then want to wait for the results of both before moving on.
This seemed like a scenario where I could easily use the async and await keywords but after looking at a couple of Hello World examples, I was surprised that I could not easily meet all of the above criteria at the same time. For example, while using the await keyword kept my UI from being non blocking and allowing my app to continue to be responsive to events while I await the completion of a method, the methods were still executing synchronously. Or if I was able to execute asynchronously, I had trouble trying to pass an input parameter to the calling method and receiving a result back.
The following seems to meet my requirements and leads me to believe that I'd be able to make great use out of it but first I'd like to get your feedback on how it can be improved.
I've played around with Threads before and found that make my code much more complex and was expecting that the "async and await" keywords would make my life easier but the following code which appears to meet my requirements does not even use these keywords. Am I using the "Parallel Task Library" here? If so, how would you contrast the functionality in this library to the async and await functionality?
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ASyncCourse
{
static class Program
{
static void Main()
{
int result1 = 0;
int result2 = 0;
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Task task1 = Task.Run(() => result1 = DoSomeWork(1));
Debug.WriteLine($"After Task1: {stopWatch.Elapsed}");
Task task2 = Task.Run(() => result2 = DoSomeOtherWork(10));
Debug.WriteLine($"After Task2: {stopWatch.Elapsed}");
Task.WaitAll(task1, task2);
stopWatch.Stop();
int sum;
sum = result1 + result2;
Debug.WriteLine($"Sum: {sum}");
Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}
private static int DoSomeOtherWork(int waitFor)
{
Thread.Sleep(waitFor * 1000);
return waitFor;
}
private static int DoSomeWork(int waitFor)
{
Thread.Sleep(waitFor * 1000);
return waitFor;
}
}
}
You can use the async and await pattern and await the cpu bound tasks, however you will need to use async Task Main() entry point
public static async Task Main()
{
var task1 = Task.Run(() => CpuWork1(1));
var task2 = Task.Run(() => CpuWork2(10));
var result1 = await task1;
var result2 = await task2;
// or
var results = await Task.WhenAll(task1, task2) ;
}
If your workloads are IO bound, then they would have the async Task<T> signature, and you would just await the methods returned tasks, similar to above (and not use Task.Run)
Complete Example
static async Task Main()
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var task1 = Task.Run(() => DoSomeWork(1));
var task2 = Task.Run(() => DoSomeOtherWork(10));
var results = await Task.WhenAll(task1, task2);
stopWatch.Stop();
Debug.WriteLine($"Sum: {results.Sum()}");
Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}
Or similarly with an async methods
static async Task Main()
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var task1 = DoSomeWorkAsync(1);
var task2 = DoSomeOtherWorkAsync(10);
var results = await Task.WhenAll(task1, task2);
stopWatch.Stop();
Debug.WriteLine($"Sum: {results.Sum()}");
Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}
private static async Task<int> DoSomeOtherWorkAsync(int waitFor)
{
// some IO bound workload
await Task.Delay(waitFor * 1000);
return waitFor;
}
private static async Task<int> DoSomeWorkAsync(int waitFor)
{
// some IO bound workload
await Task.Delay(waitFor * 1000);
return waitFor;
}
Asynchronous methods are method designed to access external resources without blocking and without wasting resources(extra threads)
Use Task.Delay(1000) to simulate asynchronous method.
private async Task<int> LoadSomething(int value)
{
await Task.Delay(value * 1000);
return value;
}
Then
var task1 = LoadSomething(2);
// executes next line without waiting for previous line to complete
var task2 = LoadSomething(5);
await Task.WhenAll(task1, task2); // takes only 5 seconds
var result1 = task1.Result;
var result2 = task2.Result;
you use await keyword. async operations perform parallelly. when we need to wait until we get the result use await before it.
Example:
var task1 = AsyncOperation1(2);
var task2 = AsyncOperation1(5);
var result1 = await task1;
var result2 = await task2;
use last two line instead of Task.WhenAll(task1, task2);
Use async on all of your methods and have them return Task.
Call await Task.Delay instead of Thread.Sleep
Then, you can use await Task.WhenAll to wait for your tasks to complete.
If you do not put await in front of your method calls, execution proceeds without waiting for a result.
You can call Result to get the result once you have awaited them.
static async Task Main()
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Task<int> task1 = DoSomeWork(1);
Console.WriteLine($"After Task1: {stopWatch.Elapsed}");
Task<int> task2 = DoSomeOtherWork(10);
Console.WriteLine($"After Task2: {stopWatch.Elapsed}");
await Task.WhenAll(task1, task2);
stopWatch.Stop();
int sum = task1.Result + task2.Result;
Console.WriteLine($"Sum: {sum}");
Console.WriteLine($"Final: {stopWatch.Elapsed}");
}
private static async Task<int> DoSomeOtherWork(int waitFor)
{
await Task.Delay(waitFor * 1000);
return waitFor;
}
private static async Task<int> DoSomeWork(int waitFor)
{
await Task.Delay(waitFor * 1000);
return waitFor;
}
output
After Task1: 00:00:00.0037888
After Task2: 00:00:00.0094963
Sum: 11
Final: 00:00:10.0126715

Task null using Task.Run and Parallel.For

I have two services that ultimately both update the same object, so we have a test to ensure that the writes to that object complete (Under the hood we have retry policies on each).
9 times out of 10, one or more of the theories will fail, with the task.ShouldNotBeNull(); always being the assertion to fail. What am i getting wrong with the async code in this sample? Why would the task be null?
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
[InlineData(20)]
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
await Task.Run(() => Parallel.For(1, iterations,
x =>
{
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}));
//Check all tasks return successful
foreach (var task in tasksA)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
foreach (var task in tasksB)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
}
There's no need for Tasks and Parrallel looping here. I'm presuming that your _api calls are IO bound? You want something more like this:
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
//fire off all the async tasks
foreach(var it in iterations){
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}
//await the results
await Task.WhenAll(tasksA).ConfigureAwait(false);
foreach (var task in tasksA)
{
//no need to get GetAwaiter(), you've awaited above.
task.Result;
}
//to get the most out of the async only await them just before you need them
await Task.WhenAll(tasksB).ConfigureAwait(false);
foreach (var task2 in tasksB)
{
task2.Result;
}
this will fire all your api calls async then block while the results return. You Parallel for and tasks are just using additional thread pool threads to zero benefit.
If _api is CPU bound you could get benefit from Task.Run but I'm guessing these are web api or something. So the Task.Run is doing nothing but using an additional thread.
As others have suggested, remove the Parallel, and await on all tasks to finish before asserting them.
I would also recommend to remove .Result from each task, and await them instead.
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var taskA = _Api.TaskA(orderResult.OrderId);
var taskB = _Api.TaskB(orderResult.OrderId);
await Task.WhenAll(taskA, taskB);
var taskAResult = await taskA;
taskAResult.ShouldNotBeNull();
taskAResult.StatusCode.ShouldBe(HttpStatusCode.OK);
var taskBResult = await taskB;
taskBResult.ShouldNotBeNull();
taskBResult.StatusCode.ShouldBe(HttpStatusCode.OK);
}

Task.WhenAny blocking

I'm not sure if I'm misunderstanding the usage of Task.WhenAny but in the following code only "0" gets printed when it should print "1" and "2" and then "mx.Name" every time a task finishes:
public async void PopulateMxRecords(List<string> nsRecords, int threads)
{
ThreadPool.SetMinThreads(threads, threads);
var resolver = new DnsStubResolver();
var tasks = nsRecords.Select(ns => resolver.ResolveAsync<MxRecord>(ns, RecordType.Mx));
Console.WriteLine("0");
var finished = Task.WhenAny(tasks);
Console.WriteLine("1");
while (mxNsRecords.Count < nsRecords.Count)
{
Console.WriteLine("2");
var task = await finished;
var mxRecords = await task;
foreach(var mx in mxRecords)
Console.WriteLine(mx.Name);
}
}
The DnsStubResolver is part of ARSoft.Tools.Net.Dns. The nsRecords list contains up to 2 million strings.
I'm not sure if I'm misunderstanding the usage of Task.WhenAny
You might be. The pattern you seem to be looking for is interleaving. In the following example, notice the important changes that I have made:
use ToList() to materialize the LINQ query results,
move WhenAny() into the loop,
use Remove(task) as each task completes, and
run the while loop as long as tasks.Count() > 0.
Those are the important changes. The other changes are there to make your listing into a runnable demo of interleaving, the full listing of which is here: https://dotnetfiddle.net/nr1gQ7
public static async Task PopulateMxRecords(List<string> nsRecords)
{
var tasks = nsRecords.Select(ns => ResolveAsync(ns)).ToList();
while (tasks.Count() > 0)
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var mxRecords = await task;
Console.WriteLine(mxRecords);
}
}

tasks not run in enumerable.repeat

I don't understand why this test shows that the lambda has run a single time. I can only see how it might yield 0 or 10000. But only once? Ideally, I'd like ALL the tasks to be executed, just like the tooltip documentation on Task.WhenAll suggests.
[Fact]
public async Task FireLotsOfQueries()
{
var counter = 0;
var taskList = Enumerable.Repeat(Task.Run(async () =>
{
++counter;
await Task.Delay(1000);
}), 10000);
await Task.WhenAll(taskList);
Assert.Equal(10000, counter);
}
Result:
Xunit.Sdk.EqualException: Assert.Equal() Failure Expected: 10000
Actual: 1
The problem is you are not creating 1000 tasks. You are creating an enumerable that contains the same task 1000 times. Try this:
public async Task FireLotsOfQueries()
{
var counter = 0;
var taskList = Enumerable.Range(0, 10000)
.Select(_=> Task.Run(async () =>
{
++counter;
await Task.Delay(1000);
}));
await Task.WhenAll(taskList);
Assert.Equal(10000, counter);
}
You will definitely need some locking around counter as this version also fails, but counter will be a value closer to 10000.

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