【问题标题】:Would 'finally' block run once when there are multiple Tasks in the try-catch?当 try-catch 中有多个任务时,'finally' 块会运行一次吗?
【发布时间】:2019-10-08 01:14:26
【问题描述】:

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

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

我从post 了解到,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);
    }
}

【问题讨论】:

  • 它只会运行一次,如果你希望它运行不止一次,请将 try catch finally 放在异步运行的逻辑中。
  • 您的问题不清楚。在您发布的代码中只有一个线程。但是你写了诸如“在try 块中运行的多个线程” 之类的东西。这种说法没有任何意义。每个线程都有自己的堆栈,自己的异常处理,因此有自己的 try/catch/finally 块。如果您不确定正确的术语或无法用英语解释您的问题是什么,请修正问题,使其包含一个好的minimal reproducible example确切地解释该代码的作用,它与你想要什么,以及具体你需要什么帮助。
  • 我同意@PeterDuniho,很难按照您提出问题的方式来帮助您。是否有多个线程进入这个try 块然后等待? myTasks 是否设置为在后台线程中运行?附带说明一下,我不知道您为什么要在抛出异常时执行看似业务逻辑的操作,但对我来说这是一种代码味道。
  • @PeterDuniho 感谢您的指导,并为造成的混乱道歉。当我输入问题时,我对看到的内容感到困惑。我已经更新了描述和代码示例,并希望澄清一些细节。
  • @V0ldek 添加了我的意图的更多详细信息,我希望在代码因远程调用失败而失败时得到通知,因此我创建了一个“票证”,类似于警报。我会在下次运行成功时自动解决警报,因为如果错误是暂时的,我不想在警报上投入太多精力。

标签: c# multithreading


【解决方案1】:

简短回答:即使多个任务抛出异常,await Task.WaitAll 也只会抛出一个异常,因此 catch 块只会执行一次。 finally 块总是对try 块的每个条目执行一次。接下来是长答案。

等待Task.WaitAll,如果任何包装的Tasks 抛出异常,则抛出一个包含来自所有Tasks 的异常的AggregateException。因此,假设您毫无例外地到达了await Task.WhenAll(tasks) 行,会发生以下情况:

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

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

await 语句在后台做了很多神奇的事情,但它绝不会导致您在多个线程上继续执行。一个线程awaits,只有一个线程捡起来继续。

【讨论】:

    【解决方案2】:

    catchfinally 块运行一次,但可以同时运行。 finally 块始终在运行,而 catch 块仅在出现异常时运行,在 finally 块之前运行。

    您的代码还有另一个问题。

    try
    {
        Ticket ticket = await QueryExistingTicketAsync();
        // ...
    }
    catch (Exception ex)
    {
        if (ticket != null)
        {
    

    您在try 上创建的ticket 和您在catch 上更新的ticket 不是同一张票。在try 上声明的ticket 具有本地范围,因此它在此块之外不可见。您可能在 try-catch-finally 块之前声明了另一个 ticket 变量,但即使在这种情况下,编译器也会抱怨双变量声明。所以无论如何你提供的代码应该是无法编译的。

    【讨论】:

    • 谢谢,您指出的票证问题是我的示例中的一个错误,我刚刚修复了它。
    猜你喜欢
    • 1970-01-01
    • 2014-11-27
    • 2011-08-31
    • 1970-01-01
    • 2018-03-04
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 2011-05-17
    相关资源
    最近更新 更多