我不知道我是否以错误的方式思考TPL,但我很难理解如何获得以下内容:
我有两个功能
Task<A> getA() { ... }
Task<B> getB(A a) { ... }
这似乎经常发生:我可以异步获得A.并且给定A,我可以异步获得B。
我无法弄清楚在TPL中将这些功能链接在一起的正确方法。
这是一次尝试:
Task<B> Combined()
{
Task<A> ta = getA();
Task<Task<B>> ttb = ta.ContinueWith(a => getB(a.Result));
return ttb.ContinueWith(x => x.Result.Result);
}
ContinueWith
是我感到困惑的地方。返回的类型是“双任务”,Task<Task<B>>
。这在某种程度上对我来说似乎不对。
更新2011-09-30:
巧合的是,我找到了对TaskExtensions.Unwrap
进行操作的扩展方法Task<Task<T>>
来提供Task<T>
。
所以在我们得到C#5.0之前,我可以在这样的情况下执行ta.ContinueWith(a =&gt; ...)。UnWrap(),其中continuation本身返回一个任务。
答案 0 :(得分:14)
您的getB
是否是一个返回Task<B>
而不是B
的方法?
问题是ContinueWith
是:
public Task<TNewResult> ContinueWith<TNewResult>(
Func<Task<TResult>, TNewResult> continuationFunction,
CancellationToken cancellationToken
)
因此,在您的情况下,由于getB
返回Task<B>
,您传递了Func<Task<A>, Task<B>>
,因此TNewResult
为Task<B>
。
如果您可以更改getB
只返回给定B
A
的{{1}},那将会有效......或者您可以使用:
return ta.ContinueWith(a => getB(a.Result).Result);
然后lambda表达式的类型为Func<Task<A>, B>
,因此ContinueWith
将返回Task<B>
。
编辑:在C#5中,您可以轻松地写道:
public async Task<B> CombinedAsync()
{
A a = await getA();
B b = await getB(a);
return b;
}
...所以这只是“只是”解决最终结果的问题。我怀疑它是这样的,但有错误处理:
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;
}
这有意义吗?
答案 1 :(得分:6)
如果您熟悉LINQ(以及它背后的Monad概念),那么下面是一个简单的Task monad,它允许您编写任务。
Monad实施:
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);
});
}
}
用法示例:
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);
}
答案 2 :(得分:2)
如果您无法使用await
,您当然可以使用Unwrap
,但它会以次优的方式处理异常。我喜欢的方法是Then
,如this article中所述。构图变得简单而优雅:
Task<B> Combined()
{
return getA().Then(getB);
}
对于那些对细节感兴趣的人,我前一段时间写了blog post关于组合异步方法的问题,以及monad如何提供优雅的解决方案。
答案 3 :(得分:1)
虽然接受的答案可能有效
Task<B> Combined()
{
Task<A> ta = getA();
Task<B> ttb = ta.ContinueWith(a => getB(a.Result)).Unwrap();
return ttb;
}
实现这一点是一种更优雅的方式。