【问题标题】:Can ThreadAbortException skip finally?ThreadAbortException 最后可以跳过吗?
【发布时间】:2013-08-02 21:13:35
【问题描述】:

我读过的所有内容都声称线程中止将在从 ThreadAbortException 结束之前执行 finally 块。我想确认这一点,以便我可以计划如何处理一些可以无限期挂起的第 3 方代码。然而,以下测试让我感到困惑:

public void runTest(DateTime deadline)
{
    testThread = new Thread(() => 
    {
        try 
        {
            Console.WriteLine("test thread started at " + DateTime.Now.ToShortTimeString());
            while (true) { }
        }
        finally
        {
            Console.WriteLine("test thread entered FINALLY at " + DateTime.Now.ToShortTimeString());
            while (true) { }
        }
    });
    testThread.Start();
    while (testThread.IsAlive && deadline.Subtract(DateTime.Now).TotalSeconds > 0)
    {
        Console.WriteLine("main thread while loop " + DateTime.Now.ToShortTimeString());
        Thread.Sleep(10000);
    }
    if (testThread.IsAlive)
        testThread.Abort();
    Console.WriteLine("main thread after abort call " + DateTime.Now.ToShortTimeString());
}

我在运行时发现控制台从未提及进入 finally 块。应用程序在 .abort 调用之后继续运行,就好像根本没有 finally 块一样。难道我做错了什么?在到达控制台的最终写入之前控制是否应该传递给 finally 块,或者执行顺序是否仍然是 finally 在单独线程中或其他什么事实的函数?

【问题讨论】:

  • 我运行了代码,得到了test thread entered FINALLY at 3:21 PM
  • 未尝试,但我怀疑代码在调试和发布/无调试器附加配置之间的行为会有所不同,因为无限循环可能会被 JIT 到发布构建中无法中断的内容。
  • 打电话给Thread.Abort 就像用枪击中司机的头部来停车一样。汽车会停下来,但不知道在此期间会发生什么。您真的不应该永远需要致电Thread.Abort。如果你这样做了,那么你需要重新审视你的应用程序的设计。
  • 几乎每个人都在我吃午饭的时候发现了这个问题。 finally 不会被跳过,它只是在另一个线程中,因此在 .abort 调用之后不一定会立即执行。我错误地认为 .abort 将是一个串行执行,它直接进入 finally 块,然后返回主线程。测试线程有时在主线程恢复后的几秒钟内实际上并没有结束。

标签: c# multithreading abort finally


【解决方案1】:

Docs sayThreadAbortException 是一个可以被捕获的特殊异常,但在 catch 块结束时会自动再次引发。引发此异常时,运行时会在结束线程之前执行所有 finally 块。因为线程可以在 finally 块中进行无限计算或调用 Thread.ResetAbort 取消中止,所以不能保证线程会永远结束。

我很确定您的线程正在被转储,因为您退出该方法并丢失了对它的引用,因此垃圾收集器会收集它。尝试让 testThread 变量成为类的字段成员,看看会发生什么。

那,或者你有一个竞争条件,因为线程并行运行:主线程在旋转的测试线程可以输出 finally 数据之前完成(异常是昂贵的并且需要时间才能到达 catch 或 finally 块)。

【讨论】:

  • 运行线程通常不会被收集。见:stackoverflow.com/questions/81730/…
  • 我的经验有所不同,但似乎输出可能是主线程和衍生线程之间的竞争条件?更新了我的答案,感谢@ebyrob的输入
  • 在 Romoku 提到它对他有用后,我又试了几次。这证实了我的怀疑,即在控制权返回主线程之前,线程不一定会中止。我在中止后添加了一个 .join ,现在它留在 finally 中,而没有在主线程中调用最终控制台写入。所以 DavidH 是对的,他们是这里发生的一些比赛条件。
【解决方案2】:

工作线程函数中的finally块在与主线程并行的工作线程上执行。这是一个竞赛条件。在 abort 调用之后,您无法判断哪个工作线程最终或主线程代码执行得更快。如果你需要同步中止,那么你必须这样写:

        if (testThread.IsAlive)
        {
            testThread.Abort();

            bool blnFinishedAfterAbort = testThread.Join(TimeSpan.FromMilliseconds(1000));
            if (!blnFinishedAfterAbort)
            {
                Console.WriteLine("Thread abort failed.");
            }
        }
        Console.WriteLine("main thread after abort call " + DateTime.Now.ToShortTimeString());

请记住,如果您启用了遗留异常处理(请参阅http://msdn.microsoft.com/en-us/library/ms228965.aspx)并且您指定了 AppDomain_UnahandledException 事件处理程序,那么 ThreadAbortException 将导致执行到该处理程序 在 finally 块之前 在工作线程函数中。这只是在中止线程时应该注意的令人沮丧和意外的执行顺序的另一个示例。

【讨论】:

    【解决方案3】:

    finally 通常不应被跳过。

    控制台应用程序(假设它是一个)可能在 finally 块运行之前退出(因为在您调用 Thread.Abort() 之后没有等待)。

    如果您将Console.ReadLine() 放在程序末尾会怎样?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-23
      • 2014-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-30
      相关资源
      最近更新 更多