Ожидает ли выполнение нескольких задач больше, чем первое исключение?

Сегодня мы с моими коллегами обсуждали, как правильно обрабатывать исключения в С# 5.0 async, и мы задавались вопросом, не ожидает ли в ожидании сразу нескольких задач исключения, которые не распаковываются средой выполнения.

Рассмотрим следующий фрагмент кода:

async Task ExceptionMethodAsync()
{
    await Task.Yield();
    throw new Exception();
}

async Task CallingMethod()
{
    try
    {
        var a = ExceptionMethodAsync();
        var b = ExceptionMethodAsync();

        await Task.WhenAll(a, b);
    }
    catch(Exception ex)
    {
        // Catches the "first" exception thrown (whatever "first" means)

    }
}

Что происходит со второй задачей сейчас? Оба будут в неисправном состоянии, но второе исключение задачи теперь наблюдается или ненаблюдается?

Ответ 1

Task.WhenAll возвращает задачу и, как и все задачи, свойство Exception содержит AggregateException, который объединяет все исключения.

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

... Из-за проблем с дочерними задачами или из-за таких комбинаторов, как Task.WhenAlll, одна задача может представлять собой несколько операций, и более чем одна из них может быть неисправна. В таком случае и с целью не потерять информацию об исключении (что может быть важно для отсрочки отсрочки), мы хотим иметь возможность представлять несколько исключений, и, таким образом, для типа оболочки мы выбрали AggregateException.

... Учитывая это и снова имея выбор всегда бросать первый или всегда бросать агрегат, для "ожидания" мы предпочитаем всегда бросать первый

из Обработка исключений задач в .NET 4.5

Вам решать, нужно ли сначала обрабатывать только await task; (true в большинстве случаев) или обрабатывать все, используя task.Exception (как в моем примере ниже), но в обоих случаях a и b не поднимет UnobservedTaskException.

var task = Task.WhenAll(a, b);
try
{
    await task;
}
catch
{
    Trace.WriteLine(string.Join(", ", task.Exception.Flatten().InnerExceptions.Select(e => e.Message)));
}