Подтвердить что ты не робот

Как заставить Task.WaitAll() сломаться, если произошло какое-либо исключение?

Я хочу, чтобы Task.WaitAll() вырвался, если какая-либо из запущенных задач выдает исключение, так что мне не нужно ждать 60 секунд, чтобы закончить. Как мне добиться такого поведения? Если WaitAll() не может этого достичь, есть ли другая функция С# или обходной путь?

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.
}
4b9b3361

Ответ 1

Следующее должно сделать это, не изменяя код исходных задач (непроверенный):

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);
}

Обратите внимание, что он отслеживает только неисправные задачи (те, которые бросаются). Если вам нужно отследить отмененные задачи, сделайте это изменение:

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

Обновлено, ожидание прокси-серверов задачи здесь избыточно, как указано в комментариях @svick. Он предлагает улучшенную версию: https://gist.github.com/svick/9992598.

Ответ 2

Параллельный класс может выполнить эту работу за вас. Вы можете использовать Parallel.For, ForEach или 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);
        }
    }
}

Если одна из этих задач выдает исключение, другая работа не будет выполнена, кроме тех, чье выполнение уже было запущено. For, ForEach и Invoke ждут завершения всех задач до возобновления управления вызывающим кодом. У вас может быть даже более тонкое управление зерном, если вы используете ParallelLoopState.IsExceptional. Parallel.Invoke больше подходит для вашего случая.

Ответ 3

Один из способов сделать это - использовать CancellationTokenSource. Вы создаете cancelationtokensource и передаете его как аргумент Task.WaitAll. Идея состоит в том, чтобы завершить вашу задачу в блоке try/catch, а в случае исключения - отменить отмену отмены.

Здесь пример кода

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.
            }

Ответ 4

Я хотел предложить небольшую модификацию превосходного ответа Noseratio выше. В моем случае мне нужно было сохранить исходное исключение, а в окружении try/catch различать отмененные и исключительные состояния.

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;
    }
}