为什么在finally块中睡眠时线程不会被中断

Mar*_*rek 9 c# multithreading interrupted-exception

我一直在寻找MSDN,并且无法找到为什么Thread在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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是MSDN声称线程可以在最终块中止:http://msdn.microsoft.com/en-us/library/aa332364(v = vs.71).aspx "线程可能会在最终阻塞时中止正在运行,在这种情况下,finally块被中止."

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

Tim*_*oyd 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...");
        }
    }
Run Code Online (Sandbox Code Playgroud)

更新

相当有趣的低级问题.尽管Abort()有文献记载,Interrupt()但更不用说了.

对你的问题的简短回答是否定的,你不能通过调用AbortInterrupt在它上面唤醒finally块中的线程.

无法在finally块中中止或中断线程是设计的,只是为了使最终块有机会按预期运行.如果你可以中止并中断finally块中的线程,这可能会对清理例程产生意想不到的后果,因此使应用程序处于损坏的状态 - 不好.

线程中断的细微差别在于,在线程进入finally块之前的任何时间都可能发出中断,但是当它没有处于某种SleepWaitJoin状态(即没有被阻塞)时.在这种情况下,如果finally块中存在阻塞调用,它将立即抛出ThreadInterruptedException并崩溃finally块.最后块阻止防止这种情况.

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

这个例外(没有双关语意思)就是所谓的Rude Aborts.这些是ThreadAbortExceptions由CLR托管环境本身引发的.这些可能导致最终和catch块退出,但不会导致CERs 退出.例如,CLR可能会引发Rude Aborts以响应其判断为执行其工作\退出的线程,例如在尝试卸载AppDomain或在SQL Server CLR中执行代码时.在您的特定示例中,当您的应用程序关闭并且AppDomain卸载时,CLR将在休眠线程上发出Rude Abort,因为存在AppDomain卸载超时.

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

退出

当调用Abortfinally块中的线程时,调用线程被阻塞.这是记录:

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

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

  1. 调用线程将Abort在此处发出一个块,直到退出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步.

摘要

因此,尽管AbortInterrupt有略微不同的行为,他们都将导致被调用线程睡眠永远,并且调用线程阻塞永远(在你的例子).

只有粗鲁的中止可以强制阻塞的线程退出finally块,而这些只能由CLR本身引发(你甚至不能使用反射来执行,ThreadAbortException.ExceptionState因为它会进行内部CLR调用以获得AbortReason- 没有机会容易变得邪恶那里...).

CLR防止用户代码导致最终块被过早地退出以用于我们自己的好处 - 它有助于防止损坏的状态.

有关行为略有不同的示例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
    }
}   
Run Code Online (Sandbox Code Playgroud)


Dav*_*rtz 5

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

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