I don't know if I am thinking in the wrong way about TPL, but I have difficulty understanding how to obtain the following:
I have two functions
Task<A> getA() { ... }
Task<B> getB(A a) { ... }
This seems to occur often: I can asyncronously get an A. And given an A, I can asynchronously get a B.
I can't figure out the correct way to chain these functions together in TPL.
Here is one attempt:
Task<B> Combined()
{
Task<A> ta = getA();
Task<Task<B>> ttb = ta.ContinueWith(a => getB(a.Result));
return ttb.ContinueWith(x => x.Result.Result);
}
The ContinueWith
is where I get confused. The returned type is a "double-Task", Task<Task<B>>
. This somehow just seems wrong to me.
UPDATE 2011-09-30:
By coincidence I found the extension method TaskExtensions.Unwrap
that operates on a Task<Task<T>>
to give a Task<T>
.
So until we get C# 5.0, I can do ta.ContinueWith(a=>...).UnWrap() in situations like this where the continuation itself returns a task.
Does your getB
have to be a method which returns Task<B>
rather than B
?
The problem is that ContinueWith
is:
public Task<TNewResult> ContinueWith<TNewResult>(
Func<Task<TResult>, TNewResult> continuationFunction,
CancellationToken cancellationToken
)
So in your case, because getB
returns Task<B>
, you're passing in a Func<Task<A>, Task<B>>
, so TNewResult
is Task<B>
.
If you can change getB
to just return a B
given an A
, that would work... or you could use:
return ta.ContinueWith(a => getB(a.Result).Result);
Then the lambda expression will be of type, Func<Task<A>, B>
so ContinueWith
will return a Task<B>
.
EDIT: In C# 5 you could easily write:
public async Task<B> CombinedAsync()
{
A a = await getA();
B b = await getB(a);
return b;
}
... so it's "just" a matter of working out what that ends up as. I suspect it's something like this, but with error handling:
public Task<B> CombinedAsync()
{
TaskCompletionSource<B> source = new TaskCompletionSource();
getA().ContinueWith(taskA => {
A a = taskA.Result;
Task<B> taskB = getB(a);
taskB.ContinueWith(t => source.SetResult(t.Result));
});
return source.Task;
}
Does that make sense?
In case you are familiar with LINQ (and the Monad concept behind it), then below is a simple Task monad which will allow you to compose the Tasks.
Monad implementation:
public static class TaskMonad
{
public static Task<T> ToTask<T>(this T t)
{
return new Task<T>(() => t);
}
public static Task<U> SelectMany<T, U>(this Task<T> task, Func<T, Task<U>> f)
{
return new Task<U>(() =>
{
task.Start();
var t = task.Result;
var ut = f(t);
ut.Start();
return ut.Result;
});
}
public static Task<V> SelectMany<T, U, V>(this Task<T> task, Func<T, Task<U>> f, Func<T, U, V> c)
{
return new Task<V>(() =>
{
task.Start();
var t = task.Result;
var ut = f(t);
ut.Start();
var utr = ut.Result;
return c(t, utr);
});
}
}
Usage example:
public static void Main(string[] arg)
{
var result = from a in getA()
from b in getB(a)
select b;
result.Start();
Console.Write(result.Result);
}
While the accepted answer would probably work
Task<B> Combined()
{
Task<A> ta = getA();
Task<B> ttb = ta.ContinueWith(a => getB(a.Result)).Unwrap();
return ttb;
}
Is a much more elegant way to implement this.
If you are unable to use await
, you can certainly use Unwrap
, but it handles exceptions sub-optimally. The method I prefer is Then
as described in this article. Composition becomes simple and elegant:
Task<B> Combined()
{
return getA().Then(getB);
}
For those interested in the details, I wrote a blog post a while ago about exactly this problem of composing asynchronous methods, and how monads provide an elegant solution.