Sequential processing of asynchronous tasks
Just for the sake of completeness, that's how I would implement the helper method suggested by Chris Sinclair:
public void RunSequential(Action onComplete, Action<Exception> errorHandler,
params Func<Task>[] actions)
{
RunSequential(onComplete, errorHandler,
actions.AsEnumerable().GetEnumerator());
}
public void RunSequential(Action onComplete, Action<Exception> errorHandler,
IEnumerator<Func<Task>> actions)
{
if(!actions.MoveNext())
{
onComplete();
return;
}
var task = actions.Current();
task.ContinueWith(t => errorHandler(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
task.ContinueWith(t => RunSequential(onComplete, errorHandler, actions),
TaskContinuationOptions.OnlyOnRanToCompletion);
}
This ensures that each subsequent task is only requested when the previous one completed successfully.
It assumes that the Func<Task>
returns an already running task.
Here's how it would work with async
:
try
{
await FooAsync();
await BarAsync();
await FubarAsync();
Console.WriteLine("All done");
}
catch(Exception e) // For illustration purposes only. Catch specific exceptions!
{
Console.WriteLine(e);
}
This would work on .NET 4.0 if you installed the (prerelease) Microsoft.Bcl.Async package.
Since you're stuck on VS2010, you can use a variant of Stephen Toub's Then
:
public static Task Then(this Task first, Func<Task> next)
{
var tcs = new TaskCompletionSource<object>();
first.ContinueWith(_ =>
{
if (first.IsFaulted) tcs.TrySetException(first.Exception.InnerExceptions);
else if (first.IsCanceled) tcs.TrySetCanceled();
else
{
try
{
next().ContinueWith(t =>
{
if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled) tcs.TrySetCanceled();
else tcs.TrySetResult(null);
}, TaskContinuationOptions.ExecuteSynchronously);
}
catch (Exception exc) { tcs.TrySetException(exc); }
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
You can use it as such:
var task = FooAsync().Then(() => BarAsync()).Then(() => FubarAsync());
task.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
var e = t.Exception.InnerException;
// exception handling
}
else
{
Console.WriteLine("All done");
}
}, TaskContinuationOptions.ExcecuteSynchronously);
Using Rx, it would look like this (assuming you don't have the async
methods already exposed as IObservable<Unit>
):
FooAsync().ToObservable()
.SelectMany(_ => BarAsync().ToObservable())
.SelectMany(_ => FubarAsync().ToObservable())
.Subscribe(_ => { Console.WriteLine("All done"); },
e => { Console.WriteLine(e); });
I think. I'm not an Rx master, by any means. :)