【问题标题】:Does Task.WhenAll wait for all the tasks in case of exceptionsTask.WhenAll 是否等待所有任务以防出现异常
【发布时间】:2018-08-30 09:38:31
【问题描述】:

我有两个任务。我用 Task.WhenAll 运行它们。如果其中一个抛出异常会发生什么?另一个会完成吗?

【问题讨论】:

  • 如果您对WhenAll 在第一次异常时立即失败的实现感兴趣,here 是一个相关问题。

标签: c# async-await task


【解决方案1】:

只需运行这段代码来测试它:

private static async Task TestTaskWhenAll()
{
    try
    {
        await Task.WhenAll(
            ShortOperationAsync(),
            LongOperationAsync()
        );
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.Message); // Short operation exception
        Debugger.Break();
    }
}

private static async Task ShortOperationAsync()
{
    await Task.Delay(1000);
    throw new InvalidTimeZoneException("Short operation exception");

}

private static async Task LongOperationAsync()
{
    await Task.Delay(5000);
    throw new ArgumentException("Long operation exception");
}

调试器将在 5 秒后停止。两个异常都被抛出,但Debugger.Break() 只被命中一次。而且,exception 的值不是AggregateException,而是InvalidTimeZoneException。这是因为 new async/await 将展开到实际异常中。你可以阅读更多here。如果您想读取其他Exceptions(不仅仅是第一个),您必须从WhenAll 方法调用返回的Task 中读取它们。

【讨论】:

  • Task.Run 用于已经是异步方法的目的是什么?
  • 如果你真的想阻塞一个线程1000毫秒,Thread.Sleep(1000)Task.Delay(1000).Wait()简单。
【解决方案2】:

另一个会完成吗?

它不会因为另一个失败而停止

但它会完成吗?

Task.When 将等待所有完成,无论是否失败。我刚刚对此进行了测试以验证 - 完成需要 5 秒:

Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000)); 

如果您想对任务进行分组,您可以创建一个“新任务”,然后等待。

Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000)); 

try 
{
    await allTasks;

} catch (Exception ex) 
{

   // ex is the 'unwrapped' actual exception
   // I'm not actually sure if it's the first task to fail, or the first in the list that failed

   // Handle all if needed
   Exceptions[] allExceptions = allTasks.Exceptions;

   // OR
   // just get the result from the task / exception
   if (getVault.Status == TaskStatus.Faulted) 
   {
       ...
   }
}

【讨论】:

    【解决方案3】:

    有同样的问题并由我自己测试。简而言之:

    • 它总是等待所有任务完成。

    • 如果在所有任务完成后有任何异常,则抛出第一个异常(如果你没有捕捉到则崩溃)。

    • 对于所有异常,保留Task.WhenAll返回的Task实例并使用Exception.InnerExceptions属性。

    这是我的测试:

        static async Task Main(string[] args)
        {
            var tasks = new[] { Foo1(), Foo2(), Foo3() };
    
            Task t = null;
            try
            {
                t = Task.WhenAll(tasks);
                await t;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
            }
    
            Console.WriteLine("All have run.");
    
            if (t.Exception != null) 
            {
                foreach (var ex in t.Exception.InnerExceptions)
                {
                    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
                }
            }
    
        }
    
        static async Task Foo1()
        {
            await Task.Delay(50);
            throw new ArgumentException("zzz");
        }
    
        static async Task Foo2()
        {
            await Task.Delay(1000);
            Console.WriteLine("Foo 2");
            throw new FieldAccessException("xxx");
        }
    
        static async Task Foo3()
        {
            for (int i = 0; i < 10; i++)
            {
                await Task.Delay(200);
                Console.WriteLine("Foo 3");
            }
        }
    

    输出:

    Foo 3
    Foo 3
    Foo 3
    Foo 3
    Foo 2
    Foo 3
    Foo 3
    Foo 3
    Foo 3
    Foo 3
    Foo 3
    ArgumentException: zzz
    All have run.
    ArgumentException: zzz
    FieldAccessException: xxx
    

    【讨论】:

    • 使用Task t = null; 初始化任务很容易出错。 Task t; 更安全,因为它强制您在 try 块中初始化变量,否则您的代码将无法编译。另请注意,在大多数情况下,t.Exception 将是null(因为希望任务在大多数情况下都能成功完成)。因此,您当前的代码将在成功案例中导致NullReferenceException
    • 在我的情况下,如果我使用Task t;,它将无法编译,因为t 的分配在try 块内。是的,我应该为 t.Exception 添加一个空检查,谢谢!
    • 你说得对,我之前的建议没有编译。在进入try 块之前初始化变量怎么样? Task t = Task.WhenAll(tasks); 否则你还应该检查t == null 100% 安全的情况,这很容易忘记。
    猜你喜欢
    • 2018-07-07
    • 2017-09-24
    • 2016-01-30
    • 2012-11-06
    • 2019-06-05
    • 1970-01-01
    • 2013-09-22
    • 2021-07-17
    • 2015-05-03
    相关资源
    最近更新 更多