Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining Parallel Tasks to an End Condition or Error Condition

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:

  • Spawn a parallel task and immediately return to the UI.
  • In the parallel task:
    • Do Process1()
    • If Process1() completes without error, do Process2()
    • If Process2() completes without error, do Process3()
    • If all tasks complete without error, do SuccessCondition()
    • If any task resulted in an error, do 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?

like image 959
David Avatar asked Mar 24 '23 13:03

David


1 Answers

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.

like image 169
Dan Bryant Avatar answered Apr 05 '23 23:04

Dan Bryant