Reputation: 218837
I'm trying to wrap my head around some of the syntax and structure for parallel tasks in C#, specifically around chaining multiple tasks and handling errors.
The specific sequence of steps I'm looking to create are:
Process1()
Process1()
completes without error, do Process2()
Process2()
completes without error, do Process3()
SuccessCondition()
ErrorCondition()
It's my understanding that I would create a Task
and call ContinueWith()
to chain more tasks, passing in a TaskContinuationOptions
flag to determine when to do that continuation. Additionally, that success/error conditions would fall through all of the continuations to the end. So I'm currently trying this:
var task = new Task(() => Process1());
var task2 = task.ContinueWith(t => Process2(), TaskContinuationOptions.OnlyOnRanToCompletion);
var task3 = task2.ContinueWith(t => Process3(), TaskContinuationOptions.OnlyOnRanToCompletion);
var task4 = task3.ContinueWith(t => SuccessCondition(t), TaskContinuationOptions.OnlyOnRanToCompletion);
var task5 = task4.ContinueWith(t => ErrorCondition(t), TaskContinuationOptions.NotOnRanToCompletion);
task.Start();
It appears to be behaving as expected, except that within ErrorCondition()
the instance of t
doesn't appear to have an exception, even if I manually threw one from within, say, Process2()
. Looking at the MSDN article for handling exceptions, it says to do this:
try
{
task.Wait();
}
catch (AggregateException ex)
{
// Handle exceptions from a collection on ex
}
However, I tried that and it doesn't seem to have the exceptions there either. Also, by calling .Wait()
in the main thread like that, am I negating the parallelism and just blocking? It appears that way in my tests.
So I guess my question is... What's the correct way to chain dependent tasks and handle an overall success/error condition here? Or, how should exceptions thrown from within these tasks be properly caught, while still returning immediately to the UI?
Upvotes: 4
Views: 898
Reputation: 203821
You would need to add a continuation to all of the tasks, 1-4, with the error handling case to allow an error in any of them to call that function.
For convenience you could create a method to add the same continuation to a collection of tasks. Here's one (feel free to add others for the other overloads of ContinueWith as needed):
public static IEnumerable<Task> ContinueWith(this IEnumerable<Task> tasks
, Action<Task> continuation, TaskContinuationOptions options)
{
return tasks.Select(task => task.ContinueWith(continuation, options))
.ToList();//important for this ToList to be here;
//we want the continuations to be added now, not when the result is iterated
}
This allows you to write:
var errorTasks = new[]{task, task2, task3, task4}
.ContinueWith(ErrorCondition, TaskContinuationOptions.NotOnRanToCompletion);
Error handling for tasks is made so much easier in C# 5.0 with async
methods though. It would allow you to transform your code into this:
public static async Task Foo()
{
try
{
await Task.Run(Process1())
await Task.Run(Process2())
await Task.Run(Process3())
SuccessCondition();
}
catch (SomeExceptionType ex)
{
HandleException(ex);
}
}
This functions just as you would think it does as per your requirements, which is awesome.
Upvotes: 3
Reputation: 27495
Note that if you're using .NET 4.5 and can use async/await, you can do this much more cleanly:
public async Task DoProcess()
{
try
{
await Task.Run(Process1);
await Task.Run(Process2);
await Task.Run(Process3);
SuccessCondition();
}
catch (Exception ex)
{
ErrorCondition(ex);
}
}
If you launch this from the UI thread, SuccessCondition and ErrorCondition will occur on the UI Thread as well. One functional difference here is that the exception you catch will not be an AggregateException; it will instead be the actual exception thrown during the awaited call that failed.
Upvotes: 4