9

我一直在寻找 MSDN 并且找不到在 finally 块内休眠时不能中断线程的原因。我曾尝试中止但没有成功。

有什么办法可以在 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 评论是最佳答案,因为这解释了为什么线程有时可以并且不能在 finally 块中被中断/中止。那就是进程关闭的时候。谢谢

4

2 回答 2

9

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

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

    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 块中中止和中断线程,这可能会对清理例程产生意想不到的后果,从而使应用程序处于损坏状态 - 不好。

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

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

对此的例外(没有双关语)是所谓的粗鲁中止。这些是ThreadAbortExceptions由 CLR 托管环境本身提出的。这些可能会导致退出 finally 和 catch 块,但不会退出CER。例如,当试图卸载 AppDomain 或在 SQL Server CLR 中执行代码时,CLR 可能会引发粗鲁中止以响应它判断为花费太长时间才能完成工作\退出的线程。在您的特定示例中,当您的应用程序关闭并且 AppDomain 卸载时,CLR 将在睡眠线程上发出粗鲁中止,因为会有 AppDomain 卸载超时。

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

中止

Abortfinally 块中调用线程时,调用线程被阻塞。这是记录在案的:

如果正在中止的线程位于代码的受保护区域(例如 catch 块、finally 块或受约束的执行区域)中,则调用 Abort 的线程可能会阻塞。

在中止的情况下,如果睡眠不是无限的:

  1. 调用线程将在此处发出一个Abortbut 块,直到退出 finally 块,即它在此处停止并且不会立即继续执行该Join语句。
  2. 被调用线程的状态设置为AbortRequested
  3. 被叫方继续睡眠。
  4. 当被调用者醒来时,由于它有一个状态,AbortRequested它将继续执行finally块代码,然后“蒸发”即退出。
  5. 当被中止的线程离开 finally 块时:没有引发异常,finally 块之后没有代码被执行,线程的状态是Aborted.
  6. 调用线程被解除阻塞,继续执行Join语句并在被调用线程退出时立即通过。

因此,鉴于您的无限睡眠示例,调用线程将在第 1 步永远阻塞。

打断

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

没有那么好记录...

  1. 调用线程将发出Interrupt继续执行。
  2. 调用线程将阻塞该Join语句。
  3. 被调用线程将其状态设置为在下一次阻塞调用时引发异常,但至关重要的是,因为它位于 finally 块中,它并没有被解除阻塞,即被唤醒。
  4. 被叫方继续睡眠。
  5. 当被调用者醒来时,它将继续执行 finally 块。
  6. 当被中断的线程离开 finally 块时,它会ThreadInterruptedException在下一个阻塞调用中抛出一个(参见下面的代码示例)。
  7. 调用线程“加入”并在被调用线程退出时继续,但是,ThreadInterruptedException步骤 6 中未处理的线程现在已经使进程变平......

因此,再次给出无限睡眠的示例,调用线程将永远阻塞,但在第 2 步。

概括

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

只有粗鲁的中止可以强制阻塞的线程退出 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
    }
}   
于 2011-08-15T12:44:58.157 回答
4

块的全部意义finally在于保存不受中断或中止影响的东西,无论如何都会运行到正常完成。允许一个finally块被中止或中断几乎会破坏这一点。遗憾的是,正如您所指出的,finally由于各种竞争条件,块可能会被中止或中断。这就是为什么你会看到很多人建议你不要中断或中止线程。

相反,使用合作设计。如果一个线程应该被中断,而不是调用Sleep,使用定时等待。而不是调用Interrupt信号线程等待的东西。

于 2011-08-15T12:33:52.560 回答