c# - 为什么在finally block 中休眠时线程不被中断

标签 c# multithreading interrupted-exception

我一直在MSDN上四处寻找,找不到找不到在finally块中休眠时无法中断Thread的原因。我尝试中止没有成功。

在finally块中休眠时,有什么方法可以唤醒线程?

Thread t = new Thread(ProcessSomething) {IsBackground = false};
t.Start();
Thread.Sleep(500);
t.Interrupt();
t.Join();

private static void ProcessSomething()
{
    try { Console.WriteLine("processing"); }
    finally
    {
        try
        {
            Thread.Sleep(Timeout.Infinite);
        }
        catch (ThreadInterruptedException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

令人惊讶的是,MSDN声称线程可以在finally块中中止:http://msdn.microsoft.com/en-us/library/aa332364(v=vs.71).aspx
“运行finally块时线程有可能中止,在这种情况下,finally块将中止。”

编辑
我发现Hans Passant的评论是最好的答案,因为这解释了为什么Thread在finally块中有时可以或不能被中断/中止。那就是进程关闭的时候。
谢谢

最佳答案

如果可能,应避免中止和中断线程,因为这会破坏正在运行的程序的状态。例如,假设您中止了一个线程,该线程持有对资源开放的锁,那么这些锁将永远不会被释放。

相反,请考虑使用信令机制,以便线程可以相互协作,从而优雅地处理阻塞和解除阻塞,例如:

    private readonly AutoResetEvent ProcessEvent = new AutoResetEvent(false);
    private readonly AutoResetEvent WakeEvent = new AutoResetEvent(false);

    public void Do()
    {
        Thread th1 = new Thread(ProcessSomething);
        th1.IsBackground = false;
        th1.Start();

        ProcessEvent.WaitOne();

        Console.WriteLine("Processing started...");

        Thread th2 = new Thread(() => WakeEvent.Set());
        th2.Start();

        th1.Join();
        Console.WriteLine("Joined");
    }

    private void ProcessSomething()
    {
        try
        {
            Console.WriteLine("Processing...");
            ProcessEvent.Set();
        }
        finally
        {
            WakeEvent.WaitOne();
            Console.WriteLine("Woken up...");
        }
    }

更新

非常有趣的低级问题。尽管已记录Abort(),但Interrupt()却少得多。

对您的问题的简短回答是“否”,您无法通过在其上调用AbortInterrupt唤醒finally块中的线程。

不能中止或中断finally块中的线程是设计使然,只是为了使finally块有机会按您期望的那样运行。如果您可以中止线程并最终中断线程,则可能会对清理例程产生意想不到的后果,从而使应用程序处于损坏状态-不好。

线程中断的一个细微差别是,线程可能在进入finally块之前的任何时间都已针对该线程发出了中断,但是该中断并未处于SleepWaitJoin状态(即未阻塞)。在这种情况下,如果在finally块中有一个阻塞调用,它将立即抛出一个ThreadInterruptedException并从finally块中退出。最后,块保护可以防止这种情况。

除了对finally块进行保护外,它还扩展到try块以及CER(Constrained Execution Region),可以在用户代码中对其进行配置,以防止在执行区域之前引发一系列异常-对于必须执行代码的关键代码块非常有用完成并延迟中止。

异常(exception)情况(无双关语)称为Rude Aborts。这些是CLR托管环境本身提出的ThreadAbortExceptions。这些可能最终导致捕获块被退出,但CER却无法退出。例如,CLR可能会响应于判断为需要花费很长时间才能完成其工作\退出的线程来提高Rude Aborts。尝试卸载AppDomain或在SQL Server CLR中执行代码时。在您的特定示例中,当您的应用程序关闭且AppDomain卸载时,CLR会在 sleep 线程上发出粗鲁中止,因为会有AppDomain卸载超时。

在用户代码中不会发生final块中的中断和中断,但是这两种情况之间的行为略有不同。

中止

在finally块中的线程上调用Abort时,调用线程被阻塞。这是documented:

The thread that calls Abort might block if the thread that is being aborted is in a protected region of code, such as a catch block, finally block, or constrained execution region.



在中止情况下,如果 sleep 不是无限的:
  • 调用线程将发出Abort,但在此处阻塞,直到退出finally块为止,即,它在此处停止并且不立即进入Join语句。
  • 被调用者线程的状态设置为AbortRequested
  • 被叫者继续 sleep 。
  • 当被叫者醒来时,由于其状态为AbortRequested,它将继续执行finally块代码,然后“蒸发”,即退出。
  • 当异常终止的线程离开了finally块时:没有引发异常,在执行finally块之后没有任何代码,并且线程的状态为Aborted
  • 调用线程被解除阻塞,继续到Join语句,并在被调用线程退出时立即通过。

  • 因此,在您的示例中具有无限 sleep 的情况下,调用线程将在步骤1永远阻塞。

    打断

    在中断情况下,如果 sleep 不是无限的:

    没有很好的记录...
  • 调用线程将发出Interrupt并继续执行。
  • 调用线程将在Join语句上阻塞。
  • 被调用者线程的状态设置为在下一次阻止调用时引发异常,但至关重要的是,由于它在finally块中,所以它并未被解除阻止(即唤醒)。
  • 被叫者继续 sleep 。
  • 当被叫者醒来时,它将继续执行finally块。
  • 当被中断的线程离开finally块时,它将在其下一个阻塞调用中抛出ThreadInterruptedException(请参见下面的代码示例)。
  • 调用线程“加入”并随着被调用线程的退出而继续,但是,步骤6中未处理的ThreadInterruptedException现在使进程变平...

  • 因此,再次给您一个无限 sleep 的示例,调用线程将永远阻塞,但在步骤2中。

    概括

    因此,尽管AbortInterrupt的行为略有不同,但它们都将导致被调用线程永远处于休眠状态,并且调用线程永远处于阻塞状态(在您的示例中)。

    只有粗鲁的中止才能迫使被阻塞的线程退出finally块,并且这些只能由CLR本身引发(您甚至不能使用反射来欺骗ThreadAbortException.ExceptionState,因为它进行内部CLR调用以获取AbortReason-没有机会被在那里容易邪恶...)。

    CLR防止用户代码导致为我们自己的利益而提前退出finally块-它有助于防止损坏状态。

    有关Interrupt的行为略有不同的示例:
    internal class ThreadInterruptFinally
    {
        public static void Do()
        {
            Thread t = new Thread(ProcessSomething) { IsBackground = false };
            t.Start();
            Thread.Sleep(500);
            t.Interrupt();
            t.Join();
        }
    
        private static void ProcessSomething()
        {
            try
            {
                Console.WriteLine("processing");
            }
            finally
            {
                Thread.Sleep(2 * 1000);
            }
    
            Console.WriteLine("Exited finally...");
    
            Thread.Sleep(0); //<-- ThreadInterruptedException
        }
    }   
    

    关于c# - 为什么在finally block 中休眠时线程不被中断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7064811/

    相关文章:

    java - 处理InterruptedException的最佳方法

    Java Lock.lock() 和 Lock.lockInterruptically()

    C# GDI+ 设置 map 模式

    java - 多线程 Java 的 Hello World

    c++ - boost::thread & 成员函数

    c++ - 如何在c++多线程环境下实现定时器

    java - 如果线程在 wait() 上被中断,是否会在到达同步块(synchronized block)之外的 catch block 之前重新获取监视器?

    c# - 如何创建返回对象属性并具有此属性名称的 lambda 表达式?

    Visual Studio 之外的 C# REPL

    c# - 在 C# 中验证增值税号