Задание необработанных исключений

Я пытаюсь понять, что происходит с исключениями, которые создаются внутри объекта задачи и никогда не обрабатываются.

На MSDN сказано, что:

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

Так что я не совсем понимаю, как эти исключения влияют на ход программы. Я думал, что эти исключения должны прервать выполнение, как только они будут удалены. Но я не могу спроектировать это поведение. В следующем фрагменте сгенерированное исключение не отображается.

// Do something ...
Task.Run (()=> {throw new Exception("Exception in the task!");});
// Do something else

Пожалуйста, кто-нибудь может объяснить, как обрабатываются необработанные исключения и как они влияют на ход программы.

Ответ 1

Вы описываете поведение, поскольку оно было в .NET 4, но вам будет сложно заставить сборку мусора и фактически наблюдать за этим поведением. Следующая цитата из отличная статья Стивена Туба по этому вопросу должна сделать еще более ясным:

Задачи отслеживают, было ли необработанное исключение "наблюдаемый." В этом контексте "наблюдаемый" означает, что код присоединился с Задачей в некотором роде, чтобы, по крайней мере, узнать о исключение. Это может вызвать Wait/WaitAll в Задаче. Это может быть проверено свойство Tasks Exception после выполнения задачи завершено. Или это может быть свойство Tasks Result. Если Задача видит, что ее исключение наблюдалось в некотором роде, жизнь хороша. Если, однако, удалены все ссылки на задачу (что делает задачу доступной для сбора мусора), и если ее исключение пока не наблюдается, Задача знает, что ее исключение никогда не будет наблюдаться. В этом случае задача использует преимущества финализация и использует вспомогательный объект для распространения необработанного исключение в потоке финализатора. С описанным поведением ранее это исключение в потоке финализатора будет обрабатываться без обработки и вызывать стандартную необработанную логику исключения, которая должна регистрировать выдает и разбивает процесс.

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

public static Task IgnoreExceptions(this Task task)
{
    task.ContinueWith(c => { var ignored = c.Exception; },
        TaskContinuationOptions.OnlyOnFaulted |
        TaskContinuationOptions.ExecuteSynchronously |
        TaskContinuationOptions.DetachedFromParent);
    return task;
}

public static Task FailFastOnException(this Task task)
{
    task.ContinueWith(c => Environment.FailFast("Task faulted", c.Exception),
        TaskContinuationOptions.OnlyOnFaulted |
        TaskContinuationOptions.ExecuteSynchronously |
        TaskContinuationOptions.DetachedFromParent);
    return task;
}

В .NET 4.5 поведение по умолчанию изменилось. Опять же, цитата из еще одной статьи Стивена Тубо по этому вопросу (спасибо mike z за то, что привлекла мое внимание в комментариях):

Чтобы разработчики могли писать асинхронный код на основе Задачи .NET 4.5 изменяет поведение исключений по умолчанию для ненаблюдаемых исключения. Хотя незаметные исключения будут по-прежнему вызывать Событие UnobservedTaskException, которое должно быть поднято (не делать этого, было бы изменение), процесс по умолчанию не будет аварийно завершен. Скорее, исключение в конечном итоге будет съедено после того, как событие будет поднято, независимо от того, соблюдает ли обработчик события исключение. Эта поведение может быть настроено, однако.

Ответ 2

Обратите внимание, что приведенный выше код не совсем корректен. Вы должны вернуть указатель на task.ContinueWith, а не task.ContinueWith задачу:

public static Task IgnoreExceptions(this Task task)
{
    var t = task.ContinueWith(c => { var ignored = c.Exception; },
        TaskContinuationOptions.OnlyOnFaulted |
        TaskContinuationOptions.ExecuteSynchronously);
    return t;
}

РЕДАКТИРОВАТЬ:

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

public Task MyServiceCall()
{
  return Task.Run(() => DoSomething()).IgnoreExceptions();
}

Этот метод действительно вызывает исключения, потому что принятый ответ возвращает начальную задачу (а не ту, которая соблюдает исключение). Это может быть проблематично для других вызовов, таких как .Wait, .WhenAll и т.д. Можно подумать, что задача никогда не будет бросать, но может.

Однако мое предложенное изменение нарушит следующее:

public void SomeMethod()
{
  var myTask = new Task(() => ...);
  myTask.IgnoreExceptions().Start();
}

Внутри мы решили устаревать этот метод расширения, поскольку он слишком запутан!