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

前台/后台线程默认情况下,显式创建的线程都是前台线程(foreground threads) 。只要有一个前台线程在运行,程序就可以保持存活不结束 。当一个程序中所有前台线程停止运行时,仍在运行的所有后台线程会被强制终止 。

这里说的 显示创建,指的是通过new Thread()创建的线程
非默认情况,指的是将Thread的IsBackground属性设置为true
static void Main (string[] args){ Thread worker = new Thread ( () => Console.ReadLine() ); if (args.Length > 0) worker.IsBackground = true; worker.Start();}
当进程以强制终止这种方式结束时,后台线程执行栈中所有finally块就会被避开 。如果程序依赖finally(或是using)块来执行清理工作,例如释放数据库/网络连接或是删除临时文件,就可能会产生问题 。为了避免这种问题,在退出程序时可以显式的等待这些后台线程结束 。有两种方法可以实现:
  • 如果是显式创建的线程,在线程上调用Join阻塞 。
  • 如果是使用线程池线程,使用信号构造,如事件等待句柄 。
在任何一种情况下,都应指定一个超时时间,从而可以放弃由于某种原因而无法正常结束的线程 。这是后备的退出策略:我们希望程序最后可以关闭,而不是让用户去开任务管理器(╯-_-)╯╧══╧
线程的 前台/后台状态 与它的 优先级/执行时间的分配无关 。
异常处理当线程开始运行后,其内部发生的异常不会抛到外面,更不会被外面的try-catch-finally块捕获到 。
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块中执行适当清理),线程中的对象就可能成为一个奇怪状态(不可用或未完全释放) 。

经验总结扩展阅读