因为两个lambda表达式都捕获相同的文本变量,所以t2被打印两次
t2
t2
每个线程都有一个Name属性,可以设置该属性以利于调试。这在Visual Studio中特别有用,因为线程的名称显示在“线程窗口”和“调试位置”工具栏中。您只需设置一个线程名称即可;稍后尝试更改它会引发异常。
静态Thread.CurrentThread属性为您提供当前正在执行的线程。在以下示例中,我们设置主线程的名称:
class ThreadNaming { static void Main() { Thread.CurrentThread.Name = "main"; Thread worker = new Thread (Go); worker.Name = "worker"; worker.Start(); Go(); } static void Go() { Console.WriteLine ("Hello from " + Thread.CurrentThread.Name); } }
默认情况下,您显式创建的线程是前台线程。只要前台线程中的任何一个正在运行,它就可以使应用程序保持活动状态,而后台线程则不会。一旦所有前台线程完成,应用程序结束,所有仍在运行的后台线程终止。
线程的前台/后台状态与其优先级或执行时间的分配无关。
您可以使用其IsBackground属性查询或更改线程的背景状态。这是一个例子:
class PriorityTest { static void Main (string[] args) { Thread worker = new Thread ( () => Console.ReadLine() ); if (args.Length > 0) worker.IsBackground = true; worker.Start(); } }
如果不带任何参数调用此程序,则工作线程将处于前台状态,并将在ReadLine语句上等待用户按Enter。同时,主线程退出,但是应用程序继续运行,因为前台线程仍然处于活动状态。
另一方面,如果将参数传递给Main(),则会为工作程序分配背景状态,并且在主线程结束(终止ReadLine)时,程序几乎立即退出。
当进程以这种方式终止时,将规避后台线程执行堆栈中的所有finally块。如果您的程序最终使用(或使用)块来执行清理工作(例如释放资源或删除临时文件),则会出现问题。为了避免这种情况,您可以在退出应用程序后显式等待此类后台线程。
有两种方法可以实现此目的:
在这两种情况下,您都应指定一个超时时间,以便在由于某种原因而拒绝完成的叛逆线程时可以放弃它。这是您的备份退出策略:最后,您希望您的应用程序关闭-无需用户从任务管理器中寻求帮助!
如果用户使用任务管理器强制结束.NET进程,则所有线程都“掉线”,就好像它们是后台线程一样。这是观察到的,而不是记录的行为,并且它可能因CLR和操作系统版本而异。
前景线程不需要这种处理,但是您必须注意避免可能导致线程无法结束的错误。应用程序无法正常退出的常见原因是活动的前台线程的存在。
线程的“优先级”属性确定相对于操作系统中其他活动线程而言,执行时间的长短如下:
枚举ThreadPriority {最低,低于正常,正常,高于正常,最高}
仅在同时激活多个线程时,这才有意义。
在提高线程的优先级之前,请仔细考虑-这可能导致诸如其他线程的资源匮乏之类的问题。
提升线程的优先级并使其无法执行实时工作,因为它仍然受到应用程序进程优先级的限制。要执行实时工作,您还必须使用System.Diagnostics中的Process类提高流程优先级(我们没有告诉您如何执行此操作):
1
2
|
using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High; |
实际上,ProcessPriorityClass.High比最高优先级低了一个等级:实时。将进程优先级设置为“实时”会指示OS,您从不希望该进程将CPU时间浪费给另一个进程。如果您的程序进入意外的无限循环,您甚至可能会发现操作系统已锁定,只剩下电源按钮可以拯救您!因此,“高”通常是实时应用程序的最佳选择。
如果您的实时应用程序具有用户界面,则提高进程优先级将给屏幕更新带来过多的CPU时间,从而减慢整个计算机的速度(尤其是在UI复杂的情况下)。降低主线程的优先级并提高进程的优先级可确保实时线程不会因屏幕重绘而被抢占,但不会解决使其他应用程序耗尽CPU时间的问题,因为操作系统仍会分配整个过程的资源不成比例。理想的解决方案是使实时工作程序和用户界面作为具有不同进程优先级的单独应用程序运行,并通过远程处理或内存映射文件进行通信。内存映射文件非常适合此任务。简而言之,我们将在C#4.0的第14和25章中解释它们的工作原理。
即使提高了流程优先级,托管环境在处理严格的实时需求方面的适用性也受到限制。除了由自动垃圾收集引起的延迟问题外,操作系统(甚至对于非托管应用程序)可能还会带来其他挑战,而这些挑战最好通过专用硬件或专用实时平台来解决。
创建线程时,作用域中的任何try / catch / finally块都与线程开始执行时无关。考虑以下程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // We'll never get here! Console.WriteLine ( "Exception!" ); } } static void Go() { throw null ; } // Throws a NullReferenceException |
此示例中的try / catch语句无效,并且新创建的线程将受到未处理的NullReferenceException的阻碍。当您认为每个线程都有一个独立的执行路径时,此行为很有意义。
补救措施是将异常处理程序移至Go方法中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public static void Main() { new Thread (Go).Start(); } static void Go() { try { // ... throw null ; // The NullReferenceException will get caught below // ... } catch (Exception ex) { // Typically log the exception, and/or signal another thread // that we've come unstuck // ... } } |