线程中止异常可以跳过 finally 代码块吗?

4

我读到的所有资料都声称,在线程上执行的中止操作将在从ThreadAbortException终止之前执行finally块。 我想确认一下这一点,以便我可以计划如何处理某些可能无限挂起的第三方代码。 然而,以下测试让我感到困惑:

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 语句块一样。我做错了什么吗?在达到最终的 console 写入之前,控制权不应该传递到 finally 语句块吗?还是执行顺序仍然由 finally 在单独的线程中这个事实所决定的?

我运行了代码,得到了测试线程于下午3:21进入FINALLY的结果。 - Dustin Kingen
我还没有尝试过,但我怀疑在Debug和release/未附加调试器的配置之间,代码的行为会有所不同,因为无限循环可能会在release版本中被JIT编译成无法中断的东西。 - Alexei Levenkov
2
调用 Thread.Abort 就像是通过向司机开枪来停止汽车。汽车会停下来,但在此期间可能发生任何事情。你真的不应该永远需要调用 Thread.Abort。如果你这样做了,那么你需要重新审视你的应用程序设计。 - Jim Mischel
在我去吃午饭的时间里,几乎每个人都发现了问题。最终并没有被跳过,它只是在另一个线程中,所以执行不一定会在调用.abort之后立即发生。我错误地认为.abort会是一个串行执行,直接到finally块,然后返回主线程。测试线程有时实际上要在主线程恢复几秒钟后才能结束。 - user1807768
3个回答

8
文档说明:ThreadAbortException是一个特殊的异常,可以捕获,但它将在catch块结束时自动再次引发。当引发此异常时,运行时会在结束线程之前执行所有finally块。由于线程可以在finally块中执行无限计算或调用Thread.ResetAbort来取消中止,因此不能保证线程将永远结束。
我很确定您的线程被丢弃是因为您退出方法并失去了对它的引用,因此它被垃圾收集器收集。尝试将testThread变量设置为类的字段成员,然后查看会发生什么。
另外,您可能存在竞争条件,因为线程并行运行:主线程在旋转的测试线程输出最终数据之前就完成了(异常很昂贵,需要时间才能到达catch或finally块)。

3
运行中的线程通常不会被回收。详情请见:https://dev59.com/uHVD5IYBdhLWcg3wHnyd - user645280
我的经验有所不同,但似乎输出可能是主线程和派生线程之间的竞争条件。我已更新我的答案,感谢你的输入 @ebyrob。 - Haney
在Romoku提到它对他有效后,我又尝试了几次。这证实了我的怀疑,即线程在控制返回主线程之前不一定会被中止。我在中止后添加了一个.join,现在它停留在finally中,从未调用主线程中的最终控制台写入。所以DavidH是正确的,在这里存在某种竞争条件。 - user1807768

6

在工作线程函数中的finally块在工作线程上执行,该线程与主线程并行。这是一种竞争条件。您无法确定在调用abort后哪个工作线程的finally或主线程代码会先执行。如果您需要同步中止,则必须添加类似以下内容的代码:

        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_UnhandledException事件处理程序,则ThreadAbortException将在工作线程函数中的finally块之前导致执行到该处理程序。这只是另一个令人沮丧和意外的执行顺序示例,在终止线程时应注意。

3

finally通常不应该被跳过。

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

如果你在程序的最后加上一个Console.ReadLine()会发生什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接