Как обрабатывать все необработанные исключения при использовании параллельной библиотеки задач?
Я использую TPL (Параллельная библиотека задач) в .NET 4.0. Я хочу иметь возможность централизовать логику обработки всех необработанных исключений, используя событие Thread.GetDomain().UnhandledException
. Однако в моем приложении событие никогда не запускается для потоков, запущенных с кодом TPL, например. Task.Factory.StartNew(...)
. Событие действительно срабатывает, если я использую что-то вроде new Thread(threadStart).Start()
.
В этой статье MSDN предлагается использовать Task # Wait(), чтобы поймать AggregateException
при работе с TPL, но это не я хочу потому что это не "централизованный" достаточно механизм.
Кто-нибудь испытывает ту же проблему вообще или это только я? У вас есть решение для этого?
Ответы
Ответ 1
Кажется, нет никакого встроенного способа справиться с этим (и ответа на этот вопрос нет почти через 2 недели). Я уже развернул какой-то пользовательский код, чтобы позаботиться об этом. Описание решения довольно длинное, поэтому я опубликовал его в своем блоге. Если вы заинтересованы, обратитесь к этот пост.
Обновление 5/7/2010:. Я нашел лучший способ сделать это, используя продолжение задачи. Я создаю class ThreadFactory
, который предоставляет событие Error, которое может быть подписано обработчиком верхнего уровня и предоставляет методы для запуска задачи, прилагаемой с надлежащим продолжением.
Код размещен здесь.
Обновление 4/18/2011: Почтовый код из сообщения в блоге в соответствии с комментариями Nifle.
internal class ThreadFactory
{
public delegate void TaskError(Task task, Exception error);
public static readonly ThreadFactory Instance = new ThreadFactory();
private ThreadFactory() {}
public event TaskError Error;
public void InvokeError(Task task, Exception error)
{
TaskError handler = Error;
if (handler != null) handler(task, error);
}
public void Start(Action action)
{
var task = new Task(action);
Start(task);
}
public void Start(Action action, TaskCreationOptions options)
{
var task = new Task(action, options);
Start(task);
}
private void Start(Task task)
{
task.ContinueWith(t => InvokeError(t, t.Exception.InnerException),
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
task.Start();
}
}
Ответ 2
Я думаю, Событие TaskScheduler.UnobservedTaskException - это то, что вы хотите:
Возникает, когда неожиданное исключение не отслеживается политика эскалации исключений, которая по умолчанию прекратила бы процесс.
Итак, это событие похоже на DomainUnhandledException
, которое вы упомянули в своем вопросе, но оно встречается только для задач.
Отметьте, что эта политика ненаблюдаемых исключений (да, это не ненаблюдаемые исключения, ребята из MS изобрели новое слово...), изменились с .NET 4.0 на .NET 4.5. В .NET 4.0 незаметное исключение приводит к завершению процесса, но в .NET 4.5 - нет. Это все потому, что новый асинхронный материал, который мы будем иметь в С# 5 и VB 11.
Ответ 3
Я вижу два варианта, которые могут использоваться для централизации обработки исключений в TPL: 1. Использование события Unobserved Task Exception для планировщика заданий. 2. Использование продолжений для задач с неисправным состоянием.
Использование события незаметной задачи для планировщика заданий.
Планировщик задач имеет событие UnobservedTaskException, к которому вы можете подписаться с помощью оператора + =.
- Примечание 1: В теле обработчика вам нужно сделать вызов SetObserved() в аргументе UnobservedTaskExceptionEventArgs, чтобы уведомить планировщика о том, что обработано исключение.
- Примечание 2: Обработчик вызывается, когда задачи собираются сборщиком мусора.
- Примечание 3: Если вы будете ждать по задаче, вы все равно будете вынуждены защищать ожидания с помощью блока try/catch.
- Примечание 4: Политика по умолчанию для необработанных исключений задачи в .Net 4.0 и 4.5 отличается.
Резюме. Этот подход хорош для задач "огонь и забвение", а также для исключения исключений из политики централизованного управления исключениями.
Использование продолжений для задач с неисправным состоянием.
С помощью TPL вы можете присоединить действия к Задаче, используя метод ContinueWith(), который принимает параметр привязки и продолжения присоединения. Это действие будет вызвано после завершения задачи и только в случаях, указанных опцией. В частности:
t.ContinueWith(c => { /* exception handling code */ }, TaskContinuationOptions.OnlyOnFaulted);
устанавливает продолжение с кодом обработки исключений в Задачу t. Этот код будет работать только в том случае, если Task t был завершен из-за необработанного исключения.
- Примечание 1: Получить значение исключения в коде обработки исключений. В противном случае он будет барботирован.
- Примечание 2: Код обработки исключений вызывается сразу после завершения задачи.
- Примечание 3: Если исключение получено в коде обработки исключений, оно будет считаться обработанным, блок try/catch при ожидании задачи не сможет его поймать.
Я думаю, что для централизованной обработки исключений лучше использовать пользовательские задачи, унаследованные от Task, с обработчиком исключений, добавленным через продолжение. И сопровождайте этот подход, используя событие Unobserved Task Exception Task Scheduler, чтобы поймать попытки использовать нестандартные задачи.