Объединение двух функций () -> Задача <A>и A-> Задача <B> - PullRequest
10 голосов
/ 22 сентября 2011

Я не знаю, неправильно ли я думаю о TPL, но мне сложно понять, как получить следующее:

У меня есть две функции

Task<A> getA() { ... }
Task<B> getB(A a) { ... }

Такое часто случается: я могу асинхронно получить А. И, получив А, я могу асинхронно получить Б.

Я не могу найти правильный способ объединения этих функций в 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 => ...). UnWrap () в подобных ситуациях, когда само продолжение возвращает задачу.

Ответы [ 4 ]

14 голосов
/ 22 сентября 2011

Есть ли у 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, это сработало бы ... или вы можете использовать:

return ta.ContinueWith(a => getB(a.Result).Result);

Тогда лямбда-выражение будет иметь тип 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;
}

Имеет ли это смысл?

6 голосов
/ 23 сентября 2011

Если вы знакомы с 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);
        }
2 голосов
/ 14 августа 2013

Если вы не можете использовать await, вы, безусловно, можете использовать Unwrap, но он обрабатывает исключения неоптимально.Я предпочитаю метод Then, как описано в этой статье .Композиция становится простой и элегантной:

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

Для тех, кто интересуется деталями, я недавно написал сообщение в блоге , в котором рассказывается именно об этой проблеме создания асинхронных методов и о том, как монадыэлегантное решение.

2 голосов
/ 04 июня 2013

Хотя принятый ответ, вероятно, будет работать

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

Это гораздо более элегантный способ реализовать это.

...