using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Collections.Generic;
public static async Task Main()
var program = new Program();
var start = DateTime.UtcNow;
await ParallelInvoker.Run(
TimeSpan.FromMilliseconds(2500),
catch (ParallelInvocationException ex)
Console.WriteLine("Exceptions:");
foreach (var item in ex.InvocationData)
Console.WriteLine("Name: {0} Exception: {1}", item.Callee.Method.Name, item.Exception.GetType().Name);
var end = DateTime.UtcNow;
Console.WriteLine("Duration: {0}", (end - start).TotalMilliseconds);
public async Task Do01(CancellationToken cancellationToken)
await Task.Delay(5000, cancellationToken);
public async Task Do02(CancellationToken cancellationToken)
await Task.Delay(1000, cancellationToken);
public Task Do03(CancellationToken cancellationToken)
throw new InvalidOperationException(nameof(Do03));
public static class ParallelInvoker
public static async Task Run(TimeSpan timeout, CancellationToken cancellationToken, params Func<CancellationToken, Task>[] callees)
_ = callees ?? throw new ArgumentNullException(nameof(callees));
using var timeoutCts = new CancellationTokenSource(timeout);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
cancellationToken = cts.Token;
var failed = new ConcurrentQueue<(Func<CancellationToken, Task> Callee, Exception Exception)>();
await Task.WhenAll(callees.Select(item => Task.Run(() => Invoke(item))));
throw new ParallelInvocationException(failed);
async Task Invoke(Func<CancellationToken, Task> callee)
await callee(cancellationToken).CanceledAsTimeout(timeoutCts.Token);
failed.Enqueue((callee, ex));
public static async Task CanceledAsTimeout(this Task self, CancellationToken timeoutCancellationToken)
catch (OperationCanceledException) when (timeoutCancellationToken.IsCancellationRequested)
throw new TimeoutException();
public class ParallelInvocationException : Exception
public ParallelInvocationException(IEnumerable<(Func<CancellationToken, Task> Callee, Exception Exception)> invocationData)
_ = invocationData ?? throw new ArgumentNullException(nameof(invocationData));
this.InvocationData = invocationData;
public IEnumerable<(Func<CancellationToken, Task> Callee, Exception Exception)> InvocationData { get; }