user1066231
user1066231

Reputation: 583

How to make an async Task continue with next task after first one completed?

How to continue with next task after the dependent one completed? Will Task.ContinueWith() help? If not, how can I effectively handle the below case without clubbing the functions into a single function? Let's say there are 4 async functions:

async Task<string> Func1(string a){}
async Task<string> Func2(string returnedStringFromFunc1 ){}
async Task<string> Func3(string b){}
async Task<string> Func4(string returnedStringFromFunc3){}

Task tsk1 = func1("abc");
Task tsk3 = func3("efg");
await tsk1; // In case tsk3 is completed early, flow is waiting here.
    // Instead I could have started tsk4. 
Task tsk2 = func2(tsk1.Result);
await tsk3;
Task tsk4 = func4(tsk3.Result);

Upvotes: 1

Views: 1709

Answers (4)

Enigmativity
Enigmativity

Reputation: 117019

Try dropping in these extremely helpful extension methods:

public static class TaskEx
{
    public static async Task<R> Select<T, R>(this Task<T> task, Func<T, R> s)
    {
        var t = await task;
        return s(t);
    }
    public static async Task<R> SelectMany<T, R>(this Task<T> task, Func<T, Task<R>> k) => await k(await task);
    public static async Task<R> SelectMany<T, U, R>(this Task<T> task, Func<T, Task<U>> k, Func<T, U, R> s)
    {
        var t = await task;
        var u = await k(t);
        return s(t, u);
    }
}

Then you can do this:

async Task Main()
{
    Task<string> q1 =
        from x in Func1("abc")
        from y in Func2(x)
        select y;

    Task<string> q2 =
        from x in Func3("efg")
        from y in Func4(x)
        select y;

    string r1 = await q1;
    string r2 = await q2;

    Console.WriteLine(r1);
    Console.WriteLine(r2);
}

Task<string> Func1(string a) => Task.FromResult(a);
Task<string> Func2(string c) => Task.FromResult($"{c}!");
Task<string> Func3(string b) => Task.FromResult(b);
Task<string> Func4(string d) => Task.FromResult($"{d}!");

I get the following:

abc!
efg!

Upvotes: 3

Theodor Zoulias
Theodor Zoulias

Reputation: 43400

Here is a more conventional way of using Enigmativity's quite handy SelectMany extension method for tasks:

Task<string> combo12 = Func1("abc").SelectMany(async x => await Func2(x));
Task<string> combo34 = Func3("efg").SelectMany(async x => await Func4(x));
await Task.WhenAll(combo12, combo34); // Await the completion of both workflows
string result12 = await combo12;
string result34 = await combo34;

The SelectMany extension method:

public static async Task<TResult> SelectMany<TSource, TResult>(
    this Task<TSource> task, Func<TSource, Task<TResult>> function)
{
    return await function(await task);
}

The code could be simplified by eliding async/await in the asynchronous delegate, like this:

Task<string> combo12 = Func1("abc").SelectMany(x => Func2(x));
Task<string> combo34 = Func3("efg").SelectMany(x => Func4(x));

...or even like this:

Task<string> combo12 = Func1("abc").SelectMany(Func2);
Task<string> combo34 = Func3("efg").SelectMany(Func4);

You could do the same thing without the SelectMany operator, but it becomes rather awkward:

Task<string> combo12 = ((Func<Task<string>>)(
    async () => await Func2(await Func1("abc"))))();
Task<string> combo34 = ((Func<Task<string>>)(
    async () => await Func4(await Func3("efg"))))();

If you want to learn more about the theory behind the SelectMany operator, you may find this article interesting: Tasks, Monads, and LINQ by Stephen Toub

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131180

You could use two separate functions, even local functions, to combine the tasks:

async Task<string> Flow1(string input)
{
    var r1=await func1(input);
    var r2=await func2(r1);
    return r2;
}

async Task<string> Flow2(string input)
{
    var r1=await func3(input);
    var r2=await func4(r1);
    return r2;
}

var task1=Flow1("abc");
var task2=Flow2("efg");

await Task.WhenAll(task1,task2);

Upvotes: 5

Igor
Igor

Reputation: 62213

You can use Task.WhenAny and depending on which task completed start the next one and then wait for the last one to complete.

Task tsk1 = func1("abc");
Task tsk3 = func3("efg");
Task tsk2 = null, tsk4 = null;

var completed = await Task.WhenAny(new [] {tsk1, tsk3});
if (completed == tsk1)
{
  tsk2 = func2(tsk1.Result);
  await tsk3;
  tsk4 = func4(tsk3.Result);
}
else
{
  tsk4 = func4(tsk3.Result);
  await tsk1;
  tsk2 = func2(tsk1.Result);
}

Upvotes: 1

Related Questions