如果可能,应避免中止和中断线程,因为这会破坏正在运行的程序的状态。例如,假设您中止了一个持有对资源开放的锁的线程,这些锁将永远不会被释放。
而是考虑使用信号机制,以便线程可以相互协作,从而优雅地处理阻塞和解除阻塞,例如:
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()
但情况要少得多。
对您的问题的简短回答是否定的,您不能通过调用Abort
或Interrupt
在其上唤醒 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 块中的中止和中断不会在用户代码中发生,但两种情况之间的行为略有不同。
中止
在Abort
finally 块中调用线程时,调用线程被阻塞。这是记录在案的:
如果正在中止的线程位于代码的受保护区域(例如 catch 块、finally 块或受约束的执行区域)中,则调用 Abort 的线程可能会阻塞。
在中止的情况下,如果睡眠不是无限的:
- 调用线程将在此处发出一个
Abort
but 块,直到退出 finally 块,即它在此处停止并且不会立即继续执行该Join
语句。
- 被调用线程的状态设置为
AbortRequested
。
- 被叫方继续睡眠。
- 当被调用者醒来时,由于它有一个状态,
AbortRequested
它将继续执行finally块代码,然后“蒸发”即退出。
- 当被中止的线程离开 finally 块时:没有引发异常,finally 块之后没有代码被执行,线程的状态是
Aborted
.
- 调用线程被解除阻塞,继续执行
Join
语句并在被调用线程退出时立即通过。
因此,鉴于您的无限睡眠示例,调用线程将在第 1 步永远阻塞。
打断
在中断情况下,如果睡眠不是无限的:
没有那么好记录...
- 调用线程将发出
Interrupt
并继续执行。
- 调用线程将阻塞该
Join
语句。
- 被调用线程将其状态设置为在下一次阻塞调用时引发异常,但至关重要的是,因为它位于 finally 块中,它并没有被解除阻塞,即被唤醒。
- 被叫方继续睡眠。
- 当被调用者醒来时,它将继续执行 finally 块。
- 当被中断的线程离开 finally 块时,它会
ThreadInterruptedException
在下一个阻塞调用中抛出一个(参见下面的代码示例)。
- 调用线程“加入”并在被调用线程退出时继续,但是,
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
}
}