C#多线程之线程基础篇( 三 )


void 异常捕获(){try{new Thread(Go).Start();// 启动t线程,执行Go方法}catch (Exception e){_testOutputHelper.WriteLine(e.Message);}}void Go() => throw null!;// 抛出空指针异常解决方案是将异常处理移到Go方法中:自己的异常,自己解决
static void Go(){try{// ...throw null;// 异常会在下面被捕获// ...}catch (Exception ex){// 一般会记录异常,或通知其它线程我们遇到问题了// ...}}AppDomain.CurrentDomain.UnhandledException 会对所有未处理的异常触发,因此它可以用于集中记录线程发生的异常,但是它不能阻止程序退出 。
void UnhandledException(){AppDomain.CurrentDomain.UnhandledException += HandleUnHandledException;new Thread(Go).Start();// 启动t线程,执行Go方法}void HandleUnHandledException(object sender, UnhandledExceptionEventArgs eventArgs){_testOutputHelper.WriteLine("我发现异常了");}并非所有线程上的异常都需要处理 , 以下情况,.NET Framework 会为你处理:

  • 异步委托(APM)
  • BackgroundWorker(EAP)
  • 任务并行库(TPL)
中断与中止所有阻塞方法Wait(), Sleep() or Join(),在阻塞条件永远无法被满足且没有指定超时时间的情况下,线程会陷入永久阻塞 。
有两个方式可以实现强行结束:中断、中止
中断(Interrupt)在一个阻塞线程上调用Thread.Interrupt会强制释放它 , 并抛出ThreadInterruptedException异常,与上文的一样 , 这个异常同样不会抛出
var t = new Thread(delegate(){try{Thread.Sleep(Timeout.Infinite);// 无期限休眠}catch (ThreadInterruptedException){_testOutputHelper.WriteLine("收到中断信号");}_testOutputHelper.WriteLine("溜溜球~");});t.Start();Thread.Sleep(3000);// 睡3s后中断线程tt.Interrupt();如果在非阻塞线程上调用Thread.Interrupt,线程会继续执行直到下次被阻塞时,抛出ThreadInterruptedException 。这避免了以下这样的代码:
if ((worker.ThreadState & ThreadState.WaitSleepJoin) > 0)// 线程不安全的{worker.Interrupt();}??随意中断一个线程是极度危险的,这可能导致调用栈上的任意方法(框架、第三方包)收到意外的中断,而不仅仅是你自己的代码!只要调用栈上发生阻塞(因为使用同步构造) , 中断就会发生在这,如果在设计时没有考虑中断(在finally块中执行适当清理),线程中的对象就可能成为一个奇怪状态(不可用或未完全释放) 。
??如果是自己设计的阻塞,完全可以用 信号构造(signal structure) 或者 取消令牌(cancellation tokens) 来达到相同效果,且更加安全 。如果希望结束他人代码导致的阻塞,Abort总是更合适
中止(Abort)通过Thread.Abort方法也可以使阻塞的线程被强制释放,效果和调用Interrupt类似 , 不同的是它抛出的是ThreadAbortException的异常 。另外,这个异常会在catch块结束时被重新抛出(试图更好的结束线程) 。
Thread t = new Thread(delegate(){try{while (true){}}catch (ThreadAbortException){_testOutputHelper.WriteLine("收到中止信号");}// 这里仍然会继续抛出ThreadAbortException , 以保证此线程真正中止});_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Unstarted 状态t.Start();Thread.Sleep(1000);_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Running 状态t.Abort();_testOutputHelper.WriteLine(t.ThreadState.ToString()); // AbortRequested 状态t.Join();_testOutputHelper.WriteLine(t.ThreadState.ToString()); // Stopped 状态除非Thread.ResetAbort在catch块中被调用,在此之前,线程状态(thread state) 是AbortRequested,调用Thread.ResetAbort来阻止异常被自动重新抛出之后,线程重新进入Running状态(从这开始,它可能被再次中止)
static void Main(){Thread t = new Thread (Work);t.Start();Thread.Sleep (1000); t.Abort();Thread.Sleep (1000); t.Abort();Thread.Sleep (1000); t.Abort();}static void Work(){while (true){try { while (true); }catch (ThreadAbortException) { Thread.ResetAbort(); }Console.WriteLine ("我没死!");}}Thread.Abort在NET 5被弃用了:https://learn.microsoft.com/zh-cn/dotnet/core/compatibility/core-libraries/5.0/thread-abort-obsolete
未处理的ThreadAbortException是仅有的两个不会导致应用程序关闭的异常之一 , 另一个是AppDomainUnloadException 。
Abort几乎对处于任何状态的线程都有效:Running、Blocked、Suspended以及Stopped 。然而,当挂起的线程被中止时,会抛出ThreadStateException异常 。中止会直到线程之后恢复时才会起作用 。
try { suspendedThread.Abort(); }catch (ThreadStateException) { suspendedThread.Resume(); }// 现在 suspendedThread 才会中止

推荐阅读