How to break the chain of Task when an exception occurs?

1.6k Views Asked by At

I create the chain of the works. They are to work inside of my additional thread. I use the Task for this purpose. Also, I want to break the chain's work if any exception occurred and throw it in the calling thread. But I see my chain wasn't broken and the act2 with act3 was completed too.

How can I fix it?

enter image description here

using System;
using System.Threading.Tasks;

namespace Bushman.Sandbox.Threads {
    class Program {
        static void Main(string[] args) {
            Console.Title = "Custom thread";

            try {
                // First work
                Action act1 = () => {
                    for (int i = 0; i < 5; i++) {

                        // I throw the exeption here
                        if (i == 3) throw new Exception("Oops!!!");

                        Console.WriteLine("Do first work");
                    }
                };

                // Second work
                Action act2 = () => {
                    for (int i = 0; i < 5; i++)
                        Console.WriteLine("  Do second work");
                };

                // Third work
                Func<int> act3 = () => {
                    for (int i = 0; i < 5; i++)
                        Console.WriteLine("    Do third work");
                    return 12345;
                };

                Task task = new Task(act1);

                // Build the chain of the works
                var awaiter = task.ContinueWith(_ => act2(),
                    TaskContinuationOptions.ExecuteSynchronously)
                    .ContinueWith(_ => act3(),
                    TaskContinuationOptions.ExecuteSynchronously)
                    .GetAwaiter();

                Console.WriteLine("Work started...");

                // launch the chain
                task.Start();

                // Here I get some result
                int result = awaiter.GetResult(); // 12345

                if (task.IsCanceled || task.IsFaulted) {
                    throw task.Exception.InnerException;
                }

                Console.WriteLine("The result: {0}",
                    result.ToString());
            }
            catch (Exception ex) {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
                Console.ResetColor();
            }

            Console.WriteLine("Press any key for exit...");
            Console.ReadKey();
        }
    }
}
2

There are 2 best solutions below

4
On BEST ANSWER

You have to use the NotOnFaulted Task Continuation Option.

Since TaskContinuationOptions is decorated with the Flags attribute, you can combine NotFaulted with other options.

 var awaiter = task.ContinueWith(_ => act2(),
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnFaulted)
                .ContinueWith(_ => act3(),
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnFaulted)
                .GetAwaiter();

Even if you are using the async/await keywords, this approach is still valid (but you get rid of GetAwaiter call)

4
On

The code is trying to use tasks in an unconventional way, almost as if they were threads. They aren't - tasks are a job that will get scheduled to run on a threadpool thread, not the thread itself. Calling Task.Start won't execute anything, it will schedule its delegate to run on a thread. That's why tasks are never created using the constructor.

The easiest way to start and coordinate tasks is to use Task.Run and async/await, eg:

public static async Task<int> MyMethodAsync()
{
    try
    {
        await Task.Run(()=>act1());
        await Task.Run(()=>act2());
        var result=await Task.Run(()=>act3());
        return result;
    }
    catch (Exception exc)
    {
           //Do something
    }
}

You can't use async/await on a console application's Main function, so you'll have to call the method in the following way:

var result=MyMethodAsync().Result;

Calling .Wait() or .Result on a task rethrows any exceptions raised inside it.

Without async/await, you'd need to use ContinueWith and actually check the result of the previous task. If you simply want to stop processing, you can pass TaskContinuationOptions.NotOnFaulted :

var result = Task.Run(()=>act1())
                 .ContinueWith( t1=>act2(),TaskContinuationOptions.NotOnFaulted)
                 .ContinueWith( t2=>act3(),TaskContinuationOptions.NotOnFaulted)
                 .Result;

You don't need to get explicit access to the awaiter. The final call to .Result will either return the integer result or throw an AggregateException if one of the previous tasks faulted