user926958
user926958

Reputation: 9565

How to make Task.WaitAll() to break if any exception happened?

I want to make Task.WaitAll() to break out if any of the running tasks throws an exception, so that I don't have to wait for 60 seconds to finish. How do I achieve such behavior? If WaitAll() cannot achieve that, is there any other c# feature or workaround?

Task task1 = Task.Run(() => throw new InvalidOperationException());
Task task2 = ...
...
try
{
    Task.WaitAll(new Task[]{task1, task2, ...}, TimeSpan.FromSeconds(60));
}
catch (AggregateException)
{
    // If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}

Upvotes: 33

Views: 10476

Answers (4)

noseratio
noseratio

Reputation: 61656

The following should do it without altering the code of the original tasks (untested):

static bool WaitAll(Task[] tasks, int timeout, CancellationToken token)
{
    var cts = CancellationTokenSource.CreateLinkedTokenSource(token);

    var proxyTasks = tasks.Select(task => 
        task.ContinueWith(t => {
            if (t.IsFaulted) cts.Cancel();
            return t; 
        }, 
        cts.Token, 
        TaskContinuationOptions.ExecuteSynchronously, 
        TaskScheduler.Current).Unwrap());

    return Task.WaitAll(proxyTasks.ToArray(), timeout, cts.Token);
}

Note it only tracks faulted tasks (those which threw). If you need to track cancelled tasks as well, make this change:

if (t.IsFaulted || t.IsCancelled) cts.Cancel();

Updated, waiting on the task proxies is redundant here, as pointed out by @svick in the comments. He proposes an improved version: https://gist.github.com/svick/9992598.

Upvotes: 20

millejos
millejos

Reputation: 321

I wanted to suggest a slight modification to Noseratio's excellent answer above. In my case I needed to preserve the original exception thrown, and in a surrounding try/catch distinguish between cancelled and exception states.

public static void WaitUnlessFault( Task[] tasks, CancellationToken token )
{
    var cts = CancellationTokenSource.CreateLinkedTokenSource(token);

    foreach ( var task in tasks ) {
        task.ContinueWith(t =>
        {
            if ( t.IsFaulted ) cts.Cancel();
        },
        cts.Token,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Current);
    }

    try {
        Task.WaitAll(tasks, cts.Token);
    }
    catch ( OperationCanceledException ex ) {
        var faultedTaskEx = tasks.Where(t => t.IsFaulted)
            .Select(t => t.Exception)
            .FirstOrDefault();

        if ( faultedTaskEx != null )
            throw faultedTaskEx;
        else
            throw;
    }
}

Upvotes: 0

mircea
mircea

Reputation: 503

Parallel class can do the job for you. You can use Parallel.For, ForEach or Invoke.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Sample_04_04_2014_01
{
    class Program
    {
        public static void Main(string[] args)
        {
            try
            {
            Parallel.For(0,20, i => {
                            Console.WriteLine(i);
                            if(i == 5)
                                throw new InvalidOperationException();
                            Thread.Sleep(100);
                         });
            }
            catch(AggregateException){}

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

If one of these tasks throws an exception then no other task will be executed excepting those whose execution was already started. For, ForEach and Invoke are waiting for all tasks to complete before to resume control to the calling code. You can have even a finer grain control if you use ParallelLoopState.IsExceptional. Parallel.Invoke is more suited for your case.

Upvotes: 0

Michael
Michael

Reputation: 3061

One way of doing that is to use CancellationTokenSource. You create cancellationtokensource, and pass it as an argument to Task.WaitAll. The idea is to wrap your task in try/catch block, and in case of exception, call cancel on cancellationtokensource.

Here's sample code

CancellationTokenSource mainCancellationTokenSource = new CancellationTokenSource();

                Task task1 = new Task(() =>
                {
                    try
                    {
                        throw new Exception("Exception message");
                    }
                    catch (Exception ex)
                    {
                        mainCancellationTokenSource.Cancel();
                    }

                }, mainCancellationTokenSource.Token);

                Task task2 = new Task(() =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(3));
                    Console.WriteLine("Task is running");

                }, mainCancellationTokenSource.Token);

                task1.Start();
                task2.Start();

                Task.WaitAll(new[] { task1, task2}, 
                             6000, // 6 seconds
                             mainCancellationTokenSource.Token
                            );
            }
            catch (Exception ex)
            {   
                // If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
            }

Upvotes: 0

Related Questions