Here is how you could create the tasks according to the diagram, using the Task.Run
method:
Task task1 = Task.Run(() =>
{
Fun3();
Fun4();
});
Task task2 = Task.Run(() =>
{
Fun1();
Fun5();
Fun6();
});
Task task3 = Task.Run(async () =>
{
await Task.WhenAll(task1, task2);
Fun7();
Fun8();
});
The Task.Run
invokes the delegate on the ThreadPool
, not on a dedicated thread. If you have some reason to create a dedicated thread for each task, you could use the advanced Task.Factory.StartNew
method with the TaskCreationOptions.LongRunning
argument, as shown here.
It should be noted that the above implementation has not an optimal behavior in case of exceptions. In case the Fun3()
fails immediately, the optimal behavior would be to stop the execution of the task2
as soon as possible. Instead this implementation will execute all three functions Fun1
, Fun5
and Fun6
before propagating the error. You could fix this minor flaw by creating a CancellationTokenSource
and invoking the Token.ThrowIfCancellationRequested
after each function, but it's going to be messy.
Another issue is that in case both task1
and task2
fail, only the exception of the task1
is going to be propagated through the task3
. Solving this issue is not trivial.
Update: Here is one way to solve the issue of partial exception propagation:
Task task3 = Task.WhenAll(task1, task2).ContinueWith(t =>
{
if (t.IsFaulted)
{
TaskCompletionSource tcs = new();
tcs.SetException(t.Exception.InnerExceptions);
return tcs.Task;
}
if (t.IsCanceled)
{
TaskCompletionSource tcs = new();
tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
return tcs.Task;
}
Debug.Assert(t.IsCompletedSuccessfully);
Fun7();
Fun8();
return Task.CompletedTask;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)
.Unwrap();
In case both task1
and task2
fail, the task3
will propagate the exceptions of both tasks.