Почему мой процесс не завершается, когда у Task есть необработанное исключение? - PullRequest
15 голосов
/ 08 августа 2011

Я создаю службу Windows с .NET 4.0.

У меня есть несколько необработанных исключений, сгенерированных в Задачах, но они не прекращают мой процесс, как указано в документации MSDN ( Параллельные Задачи - см. Ненаблюдаемые Исключения Задач).

"Если вы не дадите ошибочному заданию возможность распространить его исключения (например, путем вызова метода Wait), среда выполнения будет наращивать ненаблюдаемые исключения задачи в соответствии с текущим Политика исключений .NET, когда задача собирается сборщиком мусора. "

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

Task.Factory.StartNew(() => { throw new Exception(); } 

Служба продолжает нормально работать при вызове.

Согласно документам, финализатор Задачи сгенерирует исключение после того, как Задача будет выполнена GC, но, похоже, этого не произойдет. MSDN неоднократно заявляет, что обычная «политика исключений .NET» приводит к завершению процесса.

Почему это не останавливает мое приложение? Единственное, о чем я могу думать, так это о том, что где-то есть ссылка на задание (это лямбда ??)

Ответы [ 5 ]

10 голосов
/ 08 августа 2011

С Essential C # 4.0 , стр. 715, вам может помочь следующее:

Необработанное исключение во время выполнения Задачи будет подавлено до вызова одного из участников завершения задачи: Wait(), Result,Task.WaitAll() или Task.WaitAny(). Каждый из этих членов будет бросить любые необработанные исключения, которые произошли в задаче выполнение.

Существует несколько способов обработки исключений. посмотрите MSDN здесь .

В ответ на ваш комментарий другая цитата из той же книги объясняет, почему распространяются не :

Хотя это относительно редко, одно из исключений из общего правила (пузырится) случается на Задаче. [..] Любые исключения на основе задач выкинут из очереди финализации при выходе из приложения пойдёт подавлено. Поведение настроено таким образом, потому что часто обрабатывать такое исключение слишком сложно [...]

Чтобы преодолеть это, один из способов сделать это элегантно - создать задачу обработчика исключений и использовать ContinueWith для отслеживания после выполнения вашей задачи. Затем вы можете использовать parentTask.IsFaulted и постепенно завершать работу, даже если исключение выдается в очереди завершения при выходе из приложения.

Совет: используйте флаг OnlyOnFaulted, чтобы запускать эту задачу только при возникновении исключения.

6 голосов
/ 30 ноября 2013

.NET 4.5 внесла некоторые изменения в способ обработки UnobservedExceptions

В то время как ненаблюдаемые исключения по-прежнему будут вызывать событие UnobservedTaskException (если этого не сделать, это может привести к поломкеизменить), процесс не будет сбой по умолчанию.

Это поведение можно настроить, однако вы можете вернуться к поведению .Net 4.0, включив ThrowUnobservedTaskExceptions следующим образом:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime>
</configuration>

Рекомендуется, чтобы разработчики библиотек включали это, когдатестирование, чтобы убедиться, что они не имеют каких-либо исключений UnobservedException.В противном случае пользователи библиотеки с включенным этим параметром могут увидеть сбой своих программ.

5 голосов
/ 09 августа 2011

Как предложили @Hans и @CodeInChaos, я обнаружил, что единственный способ сбросить необработанное исключение (таким образом убивая процесс) - принудительно запустить финализатор ( Примечание : убедитесь, что вы не делаете это в ContinueWith()!):

GC.Collect(); 
GC.WaitForPendingFinalizers();

В моих конкретных обстоятельствах Задача не была собрана сборщиком мусора, потому что выполнение программы зависело от успеха Задачи. Без продолжения потока мое приложение не сделало бы ничего, чтобы вызвать сборщик мусора (выделение объектов и т. Д.).

Интересно то, что даже GC.Collect() недостаточно. Финализатор задач все еще не запущен. GC.WaitForPendingFinalizers() должен был быть вызван явно. (Я подозреваю, что не понимаю тонкостей, связанных с финализацией).

Подводя итог: Не ожидайте, что поведение Задачи TPL * * * * * * не будет наблюдаться как поведение других потоков Необработанное исключение Поведение (например, QueueUserWorkItem). В большинстве практических ситуаций вам необходимо выполнить проверку на предмет исключений из задач: вы не можете полагаться на ненаблюдаемые исключения, предлагаемые вашему вниманию, так же, как и в случае с QUWI или аналогичными, потому что вы будете видеть их только в финализаторе, который абсолютно непредсказуем.

Редактировать: см. Мой другой ответ относительно .NET 4.5

0 голосов
/ 02 марта 2017

Согласно этому хорошему сообщению в блоге , если вы хотите завершить работу приложения, как только у вас возникнет необработанное исключение в вашей задаче, вы можете продолжить свою задачу, как показано ниже:

public static Task FailFastOnException(this Task task) 
{ 
    task.ContinueWith(c => Environment.FailFast(“Task faulted”, c.Exception), 
    TaskContinuationOptions.OnlyOnFaulted | 
    TaskContinuationOptions.ExecuteSynchronously | 
    TaskContinuationOptions.DetachedFromParent); 
    return task; 
}
0 голосов
/ 08 августа 2011

Вы можете создать его с помощью TaskCreationOptions.AttachedToParent .В соответствии с вложенными задачами и дочерними задачами (MSDN) исключения затем распространяются на ваш поток.Однако я не знаю, элегантно это или нет.

Microsoft не рекомендует это "в большинстве случаев".Кто-то еще может знать, в каком случае это может быть разумно.Из той же статьи:

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

Cheers, Matthias

...