Task.WhenAny и ненаблюдаемые исключения

Допустим, у меня есть три задачи: a, b и c. Все три гарантированно вызовут исключение в случайное время от 1 до 5 секунд. Затем я пишу следующий код:

await Task.WhenAny(a, b, c);

Это, в конечном итоге, вызовет исключение из любой ошибки задачи, которая сначала вызвала бы ошибку. Поскольку здесь нет try...catch, это исключение переместится в другое место в моем коде.

Что произойдет, если оставшиеся две задачи вызовут исключение? Разве это не ненаблюдаемые исключения, которые приведут к уничтожению всего процесса? Означает ли это, что единственный способ использовать WhenAny - внутри блока try...catch, а затем каким-то образом наблюдать за оставшимися двумя задачами, прежде чем продолжить?

Дальнейшие действия: я бы хотел, чтобы ответ применим как к .NET 4.5 , так и .NET 4.0 с пакетом Async Targeting Pack (хотя в этом случае явно используется TaskEx.WhenAny).


person David Pfeffer    schedule 01.10.2012    source источник
comment
Вы пытались запустить код, чтобы посмотреть, что произойдет?   -  person Servy    schedule 01.10.2012
comment
@Servy Финализатор не бросает, но я не вижу веской причины, почему бы и нет.   -  person David Pfeffer    schedule 01.10.2012
comment
Я помню, как @JonSkeet где-то писал, что Task.WaitAny соблюдает все исключения для всех аргументов, но сейчас я не могу его найти.   -  person David Pfeffer    schedule 01.10.2012
comment
Я думаю, вы имели в виду Task.WhenAny(), await Task.WaitAny() даже не компилировать.   -  person svick    schedule 01.10.2012
comment
@svick Да, спасибо. Я исправил это.   -  person David Pfeffer    schedule 01.10.2012
comment
Здесь есть ложная посылка: Task.WhenAny и Task.WaitAny никогда не будут создавать исключения.   -  person porges    schedule 25.09.2013
comment
@porges действительно, они дают Задачу, которую никогда не бросят.   -  person Dave Van den Eynde    schedule 26.10.2016
comment
@DaveVandenEynde @porges Вызовы никогда не будут генерироваться, но await будет, поскольку он распаковывает исключение, доставленное в результате сбоя Задачи.   -  person David Pfeffer    schedule 27.10.2016
comment
@DavidPfeffer Я так не думаю. Ожидание задачи, возвращенной от WhenAny, не бросает. Он возвращает задачу, которая вызвала выполнение WhenAny до завершения, и ожидает выполнения этой задачи.   -  person Dave Van den Eynde    schedule 27.10.2016


Ответы (2)


Что произойдет, если оставшиеся две задачи вызовут исключение?

Эти Tasks будут завершены в неисправном состоянии.

Разве это не ненаблюдаемые исключения, которые приведут к уничтожению всего процесса?

Уже нет.

В .NET 4.0 деструктор Task передает свое ненаблюдаемое исключение в _ 3_, что приведет к завершению процесса, если его не обработать.

В .NET 4.5 это поведение было изменено . Теперь ненаблюдаемые исключения передаются в TaskScheduler.UnobservedTaskException, но затем они игнорируются, если не обрабатываются.

person Stephen Cleary    schedule 01.10.2012
comment
В .NET 4.0 это также не завершает процесс в финализаторе, и я не знаю почему. - person David Pfeffer; 01.10.2012
comment
Если вы установили .NET 4.5, вы работаете на .NET 4.5, даже если вы нацеливаетесь на .NET 4.0. - person Stephen Cleary; 01.10.2012
comment
Значит, пример кода действительно не работает на .NET 4.0? Есть ли способ воспроизвести это поведение на моем компьютере разработчика (4.5)? - person David Pfeffer; 01.10.2012
comment
Я верю, что пакет Async Targeting Pack фактически добавит UnobservedTaskException обработчик, предотвращающий завершение процесса (я не тестировал это, но это было поведение Async CTP для .NET 4.0), поэтому все, что использует WhenAny, будет веди себя так же. Существует параметр конфигурации приложения, чтобы сделать .NET 4.5 имитирует старое поведение .NET 4.0. - person Stephen Cleary; 01.10.2012
comment
Итак, с NET45 мне нужно явно проверять состояние задачи после возврата из WaitAny? Это очень раздражает. - person KFL; 15.12.2017
comment
И сообщение, на которое вы ссылаетесь, похоже, не объясняет, почему WhenAny не распространяет исключение. Тем не менее, я по-прежнему считаю, что WhenAny обработка исключений противоречит интуиции - если WhenAny выдает ошибку, она наблюдает за исключением. Если не бросает, исключение не наблюдается. Во всех смыслах кажется, что дизайн должен генерировать исключение. - person KFL; 15.12.2017
comment
@KFL: В моем ответе не говорится прямо о WhenAny; скорее, он отвечает на вопрос о том, что происходит с ненаблюдаемыми исключениями задач, о чем на самом деле спрашивал оператор (используя WhenAny в качестве примера). Результат WhenAny - это просто первая завершенная задача; вы можете наблюдать за этой задачей (или любой другой), когда захотите. - person Stephen Cleary; 15.12.2017
comment
@StephenCleary спасибо за разъяснения! Сообщение, на которое вы ссылаетесь, действительно хорошо прочитано! На самом деле я был сбит с толку тем фактом, что WaitAny не передает исключение, даже если первая задача завершилась ошибкой. И я изо всех сил пытаюсь найти альтернативный API, который действительно бросает в таком случае. - person KFL; 15.12.2017
comment
@KFL: await Task.WaitAny(..) - первая задача, которую нужно выполнить; чтобы наблюдать за этой задачей, вы можете использовать второе ожидание: await await Task.WhenAny(..) - person Stephen Cleary; 15.12.2017

Да, остальные исключения задач не наблюдаются. До .NET 4.5 вы обязаны их соблюдать (не уверен, как обстоят дела с .NET 4.5, но она изменилась).

Я обычно пишу себе вспомогательный метод для таких задач типа «запустил и забыл»:

    public static void IgnoreUnobservedExceptions(this Task task)
    {
        if (task.IsCompleted)
        {
            if (task.IsFaulted)
            {
                var dummy = task.Exception;
            }
            return;
        }

        task.ContinueWith(t =>
            {
                var dummy = t.Exception;
            }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
    }

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

person usr    schedule 01.10.2012
comment
В .NET 4.0 это также не завершает процесс в финализаторе, и я не знаю почему. - person David Pfeffer; 01.10.2012