Example #1
0
        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);
        }
    }