链接两个函数()->Task< A>和一个→Task< B>

本文关键字:Task 一个 链接 两个 函数 | 更新日期: 2023-09-27 18:09:27

我不知道我是否以错误的方式思考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是我感到困惑的地方。返回类型为"double-Task", Task<Task<B>>。这在我看来似乎是不对的。

更新2011-09-30:

碰巧我发现了扩展方法TaskExtensions.Unwrap,它在Task<Task<T>>上操作以得到Task<T>。因此,在c# 5.0之前,我可以在延续本身返回任务的情况下使用ta.ContinueWith(a=>…). unwrap()。

链接两个函数()->Task< A>和一个→Task< B>

你的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>>,所以TNewResultTask<B>

如果你可以改变getB只是返回B给定A,那将工作…或者你可以使用:

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;
}

明白了吗?

如果你熟悉LINQ(以及它背后的单子概念),那么下面是一个简单的任务单子,它可以让你组合任务。

单子实现:

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);
        }

而接受的答案可能会工作

Task<B> Combined()
{
    Task<A> ta = getA();
    Task<B> ttb = ta.ContinueWith(a => getB(a.Result)).Unwrap();
    return ttb;
}

是一种更优雅的实现方式。

如果您不能使用await,您当然可以使用Unwrap,但是它处理异常的方式不是最优的。我喜欢的方法是本文中描述的Then。构图变得简洁优雅:

Task<B> Combined()
{
  return getA().Then(getB);
}

对于那些对细节感兴趣的人,我不久前写了一篇博客文章,详细讨论了组合异步方法的问题,以及单子是如何提供一个优雅的解决方案的。