public static async Task <IReadOnlyCollection <GraphTaskResult> > Run(this TaskGraph tasks, int parallel, ILogger log, CancellationToken cancel) { async Task <GraphTaskResult> RunTask(GraphTask task) { var sw = Stopwatch.StartNew(); GraphTaskResult Result(Exception ex = null) => new() { Name = task.Name, FinalStatus = task.Status, Duration = sw.Elapsed, Exception = ex }; try { if (cancel.IsCancellationRequested || tasks.DependenciesDeep(task).Any(d => d.Status.In(Cancelled, Error))) { task.Status = Cancelled; return(Result()); } task.Status = Running; log = log.ForContext("Task", task.Name); await task.Run(log, cancel); if (cancel.IsCancellationRequested) { task.Status = Cancelled; } else { task.Status = Success; } return(Result()); } catch (Exception ex) { task.Status = Error; log.Error(ex, "Task {Task} failed: {Message}", task.Name, ex.Message); return(Result(ex)); } } var block = new TransformBlock <GraphTask, GraphTaskResult>(RunTask, new() { MaxDegreeOfParallelism = parallel }); var newTaskSignal = new AsyncManualResetEvent(true); async Task Producer() { while (!tasks.AllComplete) { if (cancel.IsCancellationRequested) { foreach (var t in tasks.All.Where(t => t.Status.IsIncomplete())) { t.Status = Cancelled; } } var tasksToAdd = tasks.AvailableToRun().ToList(); if (tasksToAdd.IsEmpty()) { // if no tasks are ready to start. Wait to either be signaled, or log which tasks are still running var logTimeTask = Task.Delay(1.Minutes(), cancel); await Task.WhenAny(logTimeTask, newTaskSignal.WaitAsync()); if (newTaskSignal.IsSet) { newTaskSignal.Reset(); } if (logTimeTask.IsCompleted) { log.Debug("Waiting for {TaskList} to complete", tasks.Running.Select(t => t.Name)); } } foreach (var task in tasksToAdd) { task.Status = Queued; await block.SendAsync(task); } } block.Complete(); } var producer = Producer(); if (producer.IsFaulted) { await producer; } var taskResults = new List <GraphTaskResult>(); while (await block.OutputAvailableAsync()) { var item = await block.ReceiveAsync(); taskResults.Add(item); newTaskSignal.Set(); } await Task.WhenAll(producer, block.Completion); return(taskResults); } }