Выполнять последовательность задач, один за другим
У меня есть последовательность задач, каждая из которых зависит от вывода предыдущей. Я хотел бы представить это как единственный объект Task
, результатом которого является вывод конца последовательности. (Если задачи не зависели друг от друга, я мог бы сделать это параллельно, и я использовал бы TaskFactory.ContinueWhenAll
.)
Я хотел бы реализовать этот метод:
static Task<TState> AggregateAsync<T, TState>(
IEnumerable<T> items,
TState initial,
Func<TState, T, Task<TState>> makeTask);
Как я могу эффективно выполнять задачи один за другим в последовательности? Я использую С# 4.0, поэтому я не могу использовать async
/await
, чтобы сделать это для меня.
Изменить: Я мог бы написать AggregateAsync
следующим образом:
static Task<TState> AggregateAsync<T, TState>(IEnumerable<T> items, TState initial, Func<TState, T, Task<TState>> makeTask)
{
var initialTask = Task.Factory.StartNew(() => initial);
return items.Aggregate(
initialTask,
(prevTask, item) =>
{
prevTask.Wait(); // synchronous blocking here?
return makeTask(prevTask.Result, item);
});
}
Но, конечно же, я получу пакет задач, каждый из которых блокирует синхронное ожидание одного перед ним?
Ответы
Ответ 1
Простой способ (с помощью Microsoft.Bcl.Async
):
static async Task<TState> AggregateAsync<T, TState>(
this IEnumerable<T> items,
TState initial,
Func<TState, T, Task<TState>> makeTask)
{
var state = initial;
foreach (var item in items)
state = await makeTask(state, item);
return state;
}
Трудный путь:
static Task<TState> AggregateAsync<T, TState>(
this IEnumerable<T> items,
TState initial,
Func<TState, T, Task<TState>> makeTask)
{
var tcs = new TaskCompletionSource<TState>();
tcs.SetResult(initial);
Task<TState> ret = tcs.Task;
foreach (var item in items)
{
var localItem = item;
ret = ret.ContinueWith(t => makeTask(t.Result, localItem)).Unwrap();
}
return ret;
}
Обратите внимание, что обработка ошибок более неудобна с "жестким" способом; исключение из первого элемента будет обернуто в AggregateException
каждым последующим элементом. "Легкий" способ не переносит исключения, подобные этому.
Ответ 2
Вы можете использовать Task.ContinueWith
. task
, который вы видите в приведенном ниже коде, представляет собой предыдущую (завершенную) задачу, и вы можете получить ее результат для выполнения второй задачи и т.д.
T item1 = default(T);
T item2 = default(T);
Task<TState> task1 = makeTask(initial, item1);
//create second task
task1.ContinueWith(task => makeTask(task.Result, item2).Result,
TaskContinuationOptions.OnlyOnRanToCompletion);
Edit
Извините, я пропустил эту часть
Я хотел бы представить это как один объект Task, результатом которого является вывод конца последовательности.
Для этого вам просто нужно вернуть ссылку на результат последнего вызова ContinueWith
.
Task<State> aggregate = task1.ContinueWith(
task => makeTask(task.Result, item2).Result,
TaskContinuationOptions.OnlyOnRanToCompletion);
var res = aggregate .Result; //wait synchronously for the result of the sequence