应尽可能避免中止和中断线程,因为这会破坏正在运行的程序的状态。例如,假设您中止了一个持有对资源开放的锁的线程,这些锁将永远不会被释放。
请考虑使用信号机制,以便线程可以相互协作,从而优雅地处理阻塞和解除阻塞,例如:
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 块中有阻塞调用,它将立即抛出 ThreadInterruptedException 并从 finally 块中崩溃。最后,块保护可以防止这种情况发生。
除了 finally 块中的保护外,这还扩展到 try 块和 CER (Constrained Execution Region),可以在用户代码中配置,以防止在执行区域之前引发一系列异常 - 对于关键问题非常有用必须完成并延迟中止的代码块。
这个例外(没有双关语)是所谓的Rude Aborts。这些是 CLR 托管环境本身提出的ThreadAbortExceptions。这些可能会导致退出 finally 和 catch 块,但不会 CER。例如,CLR 可能会引发 Rude Aborts 以响应它判断为花费太长时间才能完成工作的线程\退出,例如在尝试卸载 AppDomain 或在 SQL Server CLR 中执行代码时。在您的特定示例中,当您的应用程序关闭并且 AppDomain 卸载时,CLR 将在睡眠线程上发出粗鲁中止,因为会有 AppDomain 卸载超时。
finally 块中的中止和中断不会在用户代码中发生,但两种情况之间的行为略有不同。
中止
当在 finally 块中的线程上调用 Abort 时,调用线程被阻塞。这是documented:
如果正在中止的线程位于代码的受保护区域(例如 catch 块、finally 块或受约束的执行区域)中,则调用 Abort 的线程可能会阻塞。
如果睡眠不是无限期的中止情况:
- 调用线程将发出一个
Abort,但在此处阻塞,直到退出finally 块,即它在此处停止并且不会立即执行Join 语句。
- 被调用线程的状态设置为
AbortRequested。
- 被调用者继续休眠。
- 当被调用者醒来时,由于其状态为
AbortRequested,它将继续执行 finally 块代码,然后“蒸发”即退出。
- 当被中止的线程离开finally块时:不引发异常,finally块后没有代码执行,线程状态为
Aborted。
- 调用线程已解除阻塞,继续执行
Join 语句,并在被调用线程退出时立即通过。
因此,鉴于您的示例无限睡眠,调用线程将在第 1 步永远阻塞。
中断
如果睡眠不是无限期的中断情况:
没有很好的记录...
- 调用线程将发出
Interrupt 并继续执行。
- 调用线程将阻塞
Join 语句。
- 被调用线程的状态设置为在下一次阻塞调用时引发异常,但至关重要的是,由于它位于 finally 块中,因此它没有被解除阻塞,即被唤醒。
- 被调用者继续休眠。
- 当被调用者醒来时,它会继续执行 finally 块。
- 当被中断的线程离开 finally 块时,它将在下一次阻塞调用时抛出
ThreadInterruptedException(参见下面的代码示例)。
- 调用线程“加入”并在被调用线程退出时继续,但是,步骤 6 中未处理的
ThreadInterruptedException 现在已使进程变平...
再次给出您的无限睡眠示例,调用线程将永远阻塞,但在第 2 步。
总结
因此,尽管 Abort 和 Interrupt 的行为略有不同,但它们都会导致被调用线程永远休眠,并且调用线程永远阻塞(在您的示例中)。
只有粗鲁的 Abort 可以强制阻塞的线程退出 finally 块,并且这些只能由 CLR 本身引发(你甚至不能使用反射来欺骗 ThreadAbortException.ExceptionState,因为它会进行内部 CLR 调用以获取 @ 987654346@ - 那里没有机会轻易作恶...)。
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
}
}