Как обрабатывать Task.Run Exception

У меня была проблема с перехватом моего исключения из Task.Run. Я изменил свой код, и моя проблема решена. Я хочу выяснить, в чем разница между обработкой исключений внутри Task.Run этими двумя способами:

Во внешней функции я не могу поймать исключение, но во внутренней я могу его поймать.

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        });
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

void Inside()
{
    Task.Run(() =>
    {
        try
        {
            int z = 0;
            int x = 1 / z;
        }
        catch (Exception exception)
        {
            MessageBox.Show("Inside : "+exception.Message);
        }
    });
}

Ответ 1

Когда задача запускается, все исключения, которые она выбрасывает, сохраняются и повторно бросаются, когда что-то ждет результата задачи или для завершения задачи.

Task.Run() возвращает объект Task, который вы можете использовать для этого, так:

var task = Task.Run(...)

try
{
    task.Wait(); // Rethrows any exception(s).
    ...

Для более новых версий С# вы можете использовать await вместо Task.Wait():

try
{
    await Task.Run(...);
    ...

который намного опережает.


Для полноты здесь компилируемое консольное приложение, которое демонстрирует использование await:

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

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            test().Wait();
        }

        static async Task test()
        {
            try
            {
                await Task.Run(() => throwsExceptionAfterOneSecond());
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void throwsExceptionAfterOneSecond()
        {
            Thread.Sleep(1000); // Sleep is for illustration only. 
            throw new InvalidOperationException("Ooops");
        }
    }
}

Ответ 2

Идея использования Task.Wait сделает свое дело, но заставит вызывающий поток (как говорит код) ждать и, следовательно, блокировать до завершения задачи, что фактически делает код синхронным, а не асинхронным.

Вместо этого используйте параметр Task.ContinueWith для достижения результатов:

Task.Run(() =>
{
   //do some work
}).ContinueWith((t) =>
{
   if (t.IsFaulted) throw t.Exception;
   if (t.IsCompleted) //optionally do some work);
});

Если задача должна продолжаться в потоке пользовательского интерфейса, используйте параметр TaskScheduler.FromCurrentSynchronizationContext() в качестве параметра для продолжения следующим образом:

).ContinueWith((t) =>
{
    if (t.IsFaulted) throw t.Exception;
    if (t.IsCompleted) //optionally do some work);
}, TaskScheduler.FromCurrentSynchronizationContext());

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

Ответ 3

В своем внешнем коде вы проверяете, не запускает ли запуск задачи исключение, а не сам корпус задачи. Он выполняется асинхронно, и код, который его инициировал, выполняется тогда.

Вы можете использовать:

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        }).GetAwaiter().GetResult();
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

Использование .GetAwaiter().GetResult() ждет, пока задача не закончится и пройдет исключение, как они есть, и не завершает их в AggregateException.

Ответ 4

Вы можете просто подождать, а затем исключения всплывают до текущего контекста синхронизации (см. Ответ Мэтью Уотсона). Или, как упоминает Менно Йонгериус, вы можете ContinueWith с асинхронным кодом. Обратите внимание, что вы можете сделать это, только если OnlyOnFaulted исключение, используя OnlyOnFaulted продолжения OnlyOnFaulted:

Task.Run(()=> {
    //.... some work....
})
// We could wait now, so we any exceptions are thrown, but that 
// would make the code synchronous. Instead, we continue only if 
// the task fails.
.ContinueWith(t => {
    // This is always true since we ContinueWith OnlyOnFaulted,
    // But we add the condition anyway so resharper doesn't bark.
    if (t.Exception != null)  throw t.Exception;
}, default
     , TaskContinuationOptions.OnlyOnFaulted
     , TaskScheduler.FromCurrentSynchronizationContext());

Ответ 5

Для меня я хотел, чтобы моя Task.Run продолжалась после ошибки, позволяя интерфейсу взаимодействовать с ошибкой, поскольку она имеет время.

Мое (странное?) решение должно также иметь запуск Form.Timer. Моя задача. Run имеет очередь (для долговременных вещей, отличных от UI), и мой Form.Timer имеет свою очередь (для материала UI).

Поскольку этот метод уже работал у меня, было тривиально добавить обработку ошибок: если task.Run получает сообщение об ошибке, он добавляет информацию об ошибке в очередь Form.Timer, которая отображает диалоговое окно с ошибкой.