Подтвердить что ты не робот

Почему не ждет в Task.WhenВы бросаете исключение AggregateException?

В этом коде:

private async void button1_Click(object sender, EventArgs e) {
    try {
        await Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2());
    }
    catch (Exception ex) {
        // Expect AggregateException, but got InvalidTimeZoneException
    }
}

Task DoLongThingAsyncEx1() {
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

Task DoLongThingAsyncEx2() {
    return Task.Run(() => { throw new InvalidOperation();});
}

Я ожидал, что WhenAll создаст и выбросит AggregateException, поскольку по крайней мере одна из задач, которые она ожидала, выбрала исключение. Вместо этого я возвращаю единственное исключение, вызванное одной из задач.

Не всегда ли WhenAll создает AggregateException?

4b9b3361

Ответ 1

Я точно не помню, где, но где-то я читал, что с новыми ключевыми словами async/await они разворачивают AggregateException в фактическое исключение.

Итак, в блоке catch вы получаете фактическое исключение, а не агрегированное. Это помогает нам написать более естественный и интуитивно понятный код.

Это также необходимо для упрощения преобразования существующего кода в использование async/await, где много кода ожидает определенные исключения и не агрегированные исключения.

- Изменить -

Получил это:

Асинхронный праймер Билла Вагнера

Билл Вагнер сказал: (в Когда Исключения происходят)

... Когда вы используете await, код, сгенерированный компилятором, разворачивает AggregateException и создает основное исключение. Используя подождите, вы избегаете дополнительной работы для обработки типа AggregateException используемые Task.Result, Task.Wait и другие методы ожидания, определенные в Класс задачи. Это еще одна причина, по которой основные методы задачи....

Ответ 2

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

private async Task Example()
{
    var tasks = new [] { DoLongThingAsyncEx1(), DoLongThingAsyncEx2() };

    try 
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex) 
    {
        var exceptions = tasks.Where(t => t.Exception != null)
                              .Select(t => t.Exception);
    }
}

private Task DoLongThingAsyncEx1()
{
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

private Task DoLongThingAsyncEx2()
{
    return Task.Run(() => { throw new InvalidOperationException(); });
}

Ответ 3

Я знаю, что это вопрос, который уже ответил, но выбранный ответ на самом деле не решает проблему OP, поэтому я думал, что опубликую это.

Это решение дает вам общее исключение (т.е. все исключения, которые были выбраны различными задачами) и не блокирует (рабочий процесс по-прежнему асинхронен).

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception)
    {
    }

    if (task.Exception != null)
    {
        throw task.Exception;
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    await Task.Delay(100);
    throw new Exception("B");
}

Ключ состоит в том, чтобы сохранить ссылку на агрегированную задачу перед ее ожиданием, тогда вы можете получить доступ к свойству Exception, которое содержит ваше агрегированное исключение (даже если только одна задача выбрала исключение).

Надеюсь, это по-прежнему полезно. Я знаю, что сегодня у меня была эта проблема.

Ответ 4

Вы думаете о Task.WaitAll - он выдает AggregateException.

WhenAll просто бросает первое исключение из списка исключений, с которыми он сталкивается.

Ответ 5

Просто подумал, что я бы расширил ответ @Richiban, чтобы сказать, что вы также можете обрабатывать AggregateException в блоке catch, ссылаясь на него из задачи. Например:

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception ex)
    {
        // This doesn't fire until both tasks
        // are complete. I.e. so after 10 seconds
        // as per the second delay

        // The ex in this instance is the first
        // exception thrown, i.e. "A".
        var firstExceptionThrown = ex;

        // This aggregate contains both "A" and "B".
        var aggregateException = task.Exception;
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    // Extra delay to make it clear that the await
    // waits for all tasks to complete, including
    // waiting for this exception.
    await Task.Delay(10000);
    throw new Exception("B");
}

Ответ 6

В вашем коде первое исключение возвращается дизайном, как описано в http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/task-exception-handling-in-net-4-5.aspx

Что касается вашего вопроса, вы получите сообщение об ошибке AggreateException, если вы напишете такой код:

try {
    var result = Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2()).Result; 
}
catch (Exception ex) {
    // Expect AggregateException here
} 

Ответ 7

Это работает для меня

private async Task WhenAllWithExceptions(params Task[] tasks)
{
    var result = await Task.WhenAll(tasks);
    if (result.IsFaulted)
    {
                throw result.Exception;
    }
}

Ответ 8

Попробуйте этот код:

Task task = null;
try
{
    task = Task.WhenAll(...);
    await task;
 }
 catch (AggregateException aggException)
 {
     var aggException = task.Exception;
     ...
 }