Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to chain independent C# tasks?

Let's say I have two independent async functions (that I don't control) that create Task objects:

Task A();
Task B();

and some other non-async function

void X();

How do I construct a single Task chain that executes all of these in sequence and allows further continuations (that will execute after X) to be appended?

If I do this:

Task Sequential()
{
   return A()
     .ContinueWith(t => { B(); })
     .ContinueWith(t => { X(); });
}

that does not work, because B will start a new Task chain. If B takes a long time to complete, X will be executed first (along with whatever else the caller of Sequential might ContinueWith on the returned Task). I need X to be the last element in a single Task chain, with the B Task as its precedent.

If I do this:

Task Sequential()
{
   return A()
     .ContinueWith(t => { B().ContinueWith(t => { X(); }); });
}

that only partially solves the problem, because even though A, B and X will now execute in sequential order, if the caller does Sequential().ContinueWith, that continuation will execute in parallel to B and X, not after X.

like image 771
Peter Baer Avatar asked Dec 03 '16 01:12

Peter Baer


2 Answers

Is there any reason not to use await? For example,

async Task Sequential()
{
    await A();
    await B();
    X();
}
like image 60
Alex Young Avatar answered Oct 03 '22 10:10

Alex Young


Assuming that you cannot use async/await as suggested in other answers (if you can, you should), there's a nifty little extension method available to cater for this scenario since the introduction of Task in .NET 4.0: System.Threading.Tasks.TaskExtensions.Unwrap. It takes in a Task<Task> (or Task<Task<TResult>>) and "flattens" it into a contiguous Task (or Task<TResult> respectively), which represents the completion of both the outer task, and the inner task.

Using that extension method your method can be rewritten as:

Task Sequential()
{
    return A()
        .ContinueWith(t => B()).Unwrap()
        .ContinueWith(t => X()); // You said X() is "non-async", so no Unwrap here.
}

The resultant Task will represent the completion of the entire sequential chain of tasks, in the expected order.

There is also the concept of "child tasks" originally devised for this very purpose in the early days of the Task Parallel Library, but it is horrendously difficult to use and requires that you have great control over how the tasks are started, which you may not have. Still, it's worth knowing about (if only for education's sake).

like image 34
Kirill Shlenskiy Avatar answered Oct 03 '22 09:10

Kirill Shlenskiy