8

如果除了主线程执行代码之外,finally 块似乎不会执行。在这种情况下是否可以强制执行 finally?

环境:VS 2010,.Net Framework 4.0.3

class Program
{
    static void Main(string[] args)
    {
        var h = new AutoResetEvent(false);

        ThreadPool.QueueUserWorkItem(
            obj => TestProc(h));

        h.WaitOne();
    }

    private static void TestProc(EventWaitHandle h)
    {
        try
        {
            Trace.WriteLine("Try");
            h.Set();
        }
        catch(Exception)
        {
            Trace.WriteLine("Catch");
        }
        finally
        {
            Thread.Sleep(2000);
            Trace.WriteLine("Finally");
        }
    }
}

更新:

我在 MSDN 中找到了关于该案例的提及和解释:

ThreadAbortException 类 http://msdn.microsoft.com/en-us/library/system.threading.threadabortexception.aspx

当调用 Abort 方法来销毁线程时,公共语言运行时会引发 ThreadAbortException。ThreadAbortException 是一个可以被捕获的特殊异常,但它会在 catch 块结束时自动再次引发。引发此异常时,运行时会在结束线程之前执行所有 finally 块。因为线程可以在 finally 块中进行无限计算或调用 Thread.ResetAbort 来取消中止,所以不能保证线程会结束。如果要等到中止的线程结束,可以调用 Thread.Join 方法。Join 是一个阻塞调用,直到线程真正停止执行才返回。

笔记:

当公共语言运行时 (CLR) 在托管可执行文件中的所有前台线程都结束后停止后台线程时,它不会使用 Thread.Abort。因此,您不能使用 ThreadAbortException 来检测后台线程何时被 CLR 终止。


前台和后台线程 http://msdn.microsoft.com/en-us/library/h339syd0.aspx

当运行时因为进程正在关闭而停止后台线程时,线程中不会引发异常。但是,当线程因 AppDomain.Unload 方法卸载应用程序域而停止时,前台和后台线程都会引发 ThreadAbortException。


那么为什么在应用程序结束时 CLR 不使用 AppDomain.Unload 方法在主进程结束(杀死)之前卸载应用程序域?因为http://msdn.microsoft.com/en-us/library/system.appdomain.unload.aspx

当线程调用 Unload 时,目标域被标记为卸载。专用线程尝试卸载域,并且域中的所有线程都被中止。如果线程没有中止,例如因为它正在执行非托管代码,或者因为它正在执行 finally 块,那么在一段时间后,在最初调用 Unload 的线程中会抛出一个 CannotUnloadAppDomainException。如果无法中止的线程最终结束,则不会卸载目标域。因此,在 .NET Framework 2.0 版域中,不能保证 unload,因为它可能无法终止正在执行的线程。

结论:在某些情况下,我需要考虑我的代码是在后台还是前台线程中执行?在应用程序主线程结束所有工作之前,我的代码是否可能不会完成?

4

3 回答 3

17

您的代码在后台线程中运行。当您设置 时AutoResetEvent,您的单个前台线程终止(当您到达Main方法的末尾时)并且该进程被“立即”拆除。

实际上,我认为您的finally块很可能开始执行,但是由于您要做的第一件事是休眠两秒钟,因此该进程在接到您的WriteLine电话之前就退出了。

如果您的Main方法仍在运行,或者任何其他前台线程使进程保持活动状态,您会看到您的finally块正常完成。这实际上并不是“最终在其他线程上”的问题 - 这是“只有在有前台线程时进程才保持活动状态”的问题。

于 2012-03-13T07:31:27.007 回答
4

在 finally 执行之前,您可以阻止 main 方法退出。有许多可能的方法。

  • 您可以使用同步来实现这一点。例如使用 ResetEvent,类似于您已经在做的事情,或者显式创建一个线程并加入它。

  • 您可以在方法结束时只使用一个简单的 sleep 或 readline Main

    h.WaitOne();
    Console.ReadLine();
    

然后用户可以控制程序何时退出。

  • 您可以使用非后台线程代替线程池中的线程。然后程序不会退出,直到线程也终止。如果您希望程序在线程完成之前不终止,这可能是最好和最简单的选择。
于 2012-03-13T07:32:09.863 回答
0

一段时间以来,我遇到了同样的问题,经过几次不同的尝试,以下方法奏效了:参考链接https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2124?view=vs-2019

本质上,它在您的 Main() 中。我一直在使用它,它按预期为我清理了一切。

try { try {} finally {} } catch {}

这样,您最终清理的代码就会运行,它仍然会捕获任何异常。

于 2020-07-28T18:32:57.253 回答