Как обрабатывать 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, которая отображает диалоговое окно с ошибкой.