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

Правильный способ обработки исключения в задаче

Пожалуйста, просмотрите следующий код -

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);
    Console.ReadKey();
    Console.WriteLine("Hello");
}

private static int div(int x, int y)
{
    if (y == 0)
    {
        throw new ArgumentException("y");
    }
    return x / y;
}

Если я выполняю код в режиме деблокирования, на выходе появляется "Одна или несколько ошибок", и как только я нажимаю клавишу "Enter", также появляется сообщение "Hello". Если я запускаю код в режиме отладки, вывод аналогично режиму освобождения. Но при отладке в среде IDE появляется сообщение об исключении IDE ( "Необработанное исключение в коде пользователя" ), когда элемент управления выполняет строку

throw new ArgumentException("y"); 

Если я продолжу работу, программа не выйдет из строя и отобразит тот же вывод, что и режим выпуска. Это правильный способ справиться с исключением?

4b9b3361

Ответ 1

Вам, вероятно, не нужны отдельные обработчики OnlyOnFaulted и OnlyOnRanToCompletion, и вы не обрабатываете OnlyOnCanceled. Подробнее см. этот ответ.

Но при отладке в среде IDE сообщение об ошибке IDE ( "Необработанный исключение в коде пользователя" ) появляется, когда элемент управления выполняет строку

Вы видите исключение в отладчике, потому что вы, вероятно, включили его в параметрах Debug/Exceptions (Ctrl + Alt + E).

Если я продолжу работу, программа не выйдет из строя и не отобразит тот же выход, что и режим освобождения. Это правильный способ справиться исключение?

Исключение, которое было выбрано, но не обработано внутри действия Task, не будет автоматически повторно выбрано. Вместо этого он будет завернут для будущего наблюдения как Task.Exception (типа AggregateException). Вы можете получить исходное исключение как Exception.InnerException:

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;

Чтобы сделать сбой программы в этом случае, вам действительно нужно наблюдать исключение вне действия задачи, например. путем ссылки на Task.Result:

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);

    Console.ReadKey();

    Console.WriteLine("result: " + task.Result); // will crash here

    // you can also check task.Exception

    Console.WriteLine("Hello");
}

Подробнее: Задачи и необработанные исключения, Исключение задачи Обработка в .NET 4.5.

Обновлен для адреса комментария: вот как это сделать в приложении UI с .NET 4.0 и VS2010:

void Button_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => 
    {
        return div(32, 0); 
    }).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            // faulted with exception
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            MessageBox.Show("Error: " + ex.Message);
        }
        else if (t.IsCanceled)
        {
            // this should not happen 
            // as you don't pass a CancellationToken into your task
            MessageBox.Show("Canclled.");
        }
        else
        {
            // completed successfully
            MessageBox.Show("Result: " + t.Result);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

Пока вы нацеливаетесь на .NET 4.0, и вы хотите, чтобы поведение .NET 4.0 для незаметных исключений (т.е. повторного броска, когда задача получает сбор мусора), вы должны явно настроить его в app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>

Проверьте это для более подробной информации:

Незаметные исключения задач в .NET4

Ответ 2

У вас есть AggregateException. Это отбрасывается из задач и требует проверки внутренних исключений, чтобы найти конкретные. Вот так:

task.ContinueWith(t =>
{
    if (t.Exception is AggregateException) // is it an AggregateException?
    {
        var ae = t.Exception as AggregateException;

        foreach (var e in ae.InnerExceptions) // loop them and print their messages
        {
            Console.WriteLine(e.Message); // output is "y" .. because that what you threw
        }
    }
},
TaskContinuationOptions.OnlyOnFaulted);

Ответ 3

"Возникла одна или несколько ошибок" из исключения оболочки, созданного пулом задач. Используйте Console.WriteLine(t.Exception.ToString()) для печати всего исключения, если вам это нужно.

IDE могут автоматически фиксировать все исключения независимо от того, были ли они обработаны или нет.

Ответ 4

Поскольку вы используете задачи, вы должны получить AggregateException, который обертывает все исключения, возникшие во время выполнения. Вы видите сообщение One or more errors occurred, потому что оно по умолчанию выводит метод AggregateException.ToString().

Вам нужен Handle метод экземпляра исключения.

Также см. правильный подход для обработки таких исключений здесь.

Ответ 5

        try
        {
            var t1 = Task.Delay(1000);

            var t2 = t1.ContinueWith(t =>
            {
                Console.WriteLine("task 2");
                throw new Exception("task 2 error");
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            var t3 = t2.ContinueWith(_ =>
            {
                Console.WriteLine("task 3");
                return Task.Delay(1000);
            }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap();

            // The key is to await for all tasks rather than just
            // the first or last task.
            await Task.WhenAll(t1, t2, t3);
        }
        catch (AggregateException aex)
        {
            aex.Flatten().Handle(ex =>
                {
                    // handle your exceptions here
                    Console.WriteLine(ex.Message);
                    return true;
                });
        }