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

Я использую 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, чтобы поймать попытки использовать нестандартные задачи.