Run sequence of tasks, one after the other
You can use Task.ContinueWith
. The task
you see in the code below, represents the previous (completed) task, and you can fetch its result to execute the second task, and so on.
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
Sorry, I missed this part
I'd like to represent this as a single Task object, whose result is the output of the end of the sequence.
In order to do that, you just have to return a reference to the result of the last ContinueWith
call.
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
The easy way (using 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;
}
The hard way:
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;
}
Note that error handling is more awkward with the "hard" way; an exception from the first item will be wrapped in an AggregateException
by each successive item. The "easy" way does not wrap exceptions like this.