Reputation: 583
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
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
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
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
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