The AwaitCompletion
extention method below does what you're looking for, with a more generic API. It returns a Task
, so you'll still have to await that, either via await
or Task.Wait()
or something else.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public static class TaskExtensions
{
public static Task AwaitCompletion<T>(this ICollection<Task<T>> unfinishedTasks)
{
int remainingCount = unfinishedTasks.Count;
var promise = new TaskCompletionSource<ICollection<Task<T>>>();
var finishers = unfinishedTasks.Select(x => x.ContinueWith((task, state) =>
{
int postCount = Interlocked.Decrement(ref remainingCount);
if (postCount == 0)
{
promise.SetResult(unfinishedTasks);
}
}, remainingCount));
// force evaluation
var _ = finishers.ToList();
return promise.Task;
}
/// <summary>
/// Unlike Task.Value, this doesn't wait for completion. Hence, it also doesn't
/// throw exceptions.
/// </summary>
/// <returns>Value if completed, otherwise null (or default value if a value type)</returns>
public static T GetResultOrDefault<T>(this Task<T> self)
{
if (self.IsCompletedSuccessfully)
{
return self.Result;
}
else
{
return default(T);
}
}
}
After completion of the returned Task
value, all tasks in unfinishedTasks
will either be completed or error'd (or still going if you use Task.Wait()
with a timeout). AwaitCompletion
does not throw exceptions, and awaiting it will only throw exceptions if a timeout is reached. To retrieve exceptions, look at the tasks you passed in.
Usage:
Task[] tasksToWaitFor = ...
await tasksToWaitFor.AwaitCompletion()
foreach (var task in tasksToWaitFor)
{
Console.WriteLine("result: {}", task.GetResultOrDefault());
}
The GetResultOrDefault
extension method is important. If you hit Value
directly, it'll throw AggregateException when the task fails, OTOH GetResultOrDefault
will return null.