首页 > 解决方案 > 当 try-catch 中有多个任务时,'finally' 块会运行一次吗?

问题描述

我有以下定期运行的 C# 代码,例如每 1 小时运行一次。每次运行时,它都会尝试并行进行一些远程调用。当这些远程调用中的任何一个引发异常时,我只创建一个票证,或更新之前的票证。并且在 finally 块中,如果这次没有异常,则解决现有的先前票证。本质上,我想确保无论有多少远程调用失败,我只有一张票可以调查。并且当下次所有调用都成功时,票证会自动解决。

但是,当远程调用在 try 块中全部成功时,finally 块中的调用尝试解析票证,但遇到 HTTP 412 Precondition Failed,这意味着我在 try 块中获得的票证在 finally 块之前以某种方式更新。如果它在同一个线程中更新,我不会尝试解决它。

我从这篇文章中了解到,即使存在失败(错误或取消的任务) ,Task.WhenAll也会等待所有任务完成。如果多个任务抛出异常,catch 块会运行一次还是多次?finally 块呢?

Ticket ticket = null;
try
{
    // Query to get the existing ticket if there is any.
    ticket = await QueryExistingTicketAsync();

    // Make a lot of remote calls in parallel
    var myTasks = new List<Task>();
    myTasks.Add(RemoteCallAsync("call1"));
    myTasks.Add(RemoteCallAsync("call2"));
    myTasks.Add(RemoteCallAsync("call3"));
    // ... add more Tasks for RemoteCallAsync()

    await Task.WhenAll(myTasks);
}
catch (Exception ex)
{
    if (ticket != null)
    {
        ticket.ReChecked = true;
        ticket.LastCheckTime = DateTimeOffset.Now;

        // If the previous ticket exists, meaning the last run failed as well, 
        // update the timestamp on that ticket.
        ticket = await UpdateExistingTicketAsync(ticket);
    }
    else
    {
        // If the previous ticket does not exist yet, 
        // create one ticket for investigation, this ticket.ReChecked will be true
        ticket = await CreateNewTicketAsync();
    }
}
finally
{
    // Resolve the previous ticket if it was not created/updated in catch block
    if (ticket != null && !ticket.ReChecked)
    {
        ticket.Status = "Resolved";
        await UpdateExistingTicketAsync(ticket);
    }
}

标签: c#multithreading

解决方案


简短的回答:即使多个任务抛出异常,也只会抛出一个异常await Task.WaitAll,因此该catch块只会执行一次。块总是在finally每个进入块时执行一次try。长答案如下。

等待 aTask.WaitAll时,如果任何包装Task的 s 抛出异常,AggregateException则从所有Tasks 中抛出包含异常。所以假设你已经await Task.WhenAll(tasks)毫无例外地到达了这条线,这就是发生的事情:

  1. Task.WhenAll将等待所有Tasks 完成,无论它们是否出错。
  2. 如果所有的Tasks 都成功完成,则Task.WhenAll成功完成,在到达块的末尾处继续执行,到达块的await末尾,块执行。tryfinally
  3. 如果一个或多个Tasks 以异常结束,Task.WhenAll则将它们全部包装成 anAggregateException并以故障状态完成。执行在 处恢复,并抛出await上述内容。AggregateException执行catch块,然后执行finally块。

因此,假设一个线程进入try块,在这两种情况下,当await只有一个线程继续执行时,都会继续执行。两次输入catchorfinally块的唯一方法是执行该方法两次。

await语句在幕后做了很多魔术,但它永远不会导致您在多个线程上继续执行。一个线程await,只有一个线程捡起来继续。


推荐阅读