在这一节,我将展示如何创建线程和如何使用异步来执行计算限制的操作。在这开始之前,我强调你要避免使用我给你展示的这种技术。作为代替,你应该尽量使用CLR线程池来异步执行计算限制的操作,我会在26章“计算限制的异步模式”来详细阐述。
然而,有一些情况你可能需要明确创建线程来执行一个特殊的计算限制的操作。典型的,如果你执行代码需要有特殊状态的线程,并与线程池线程不同,你可以创建一个专用线程。例如,在以下条件为真是明确创建你自己的线程:
- 你需要线程允许一个非普通优先级。所有的线程池线程都允许在普通优先级。当然,这你可以改变,但是不推荐,在线程池操作过程中,优先级的改变不会持续。
- 你需要线程作为前台线程运转,从而防止程序终止一直到线程完成任务。欲了解更多信息,参见“前台线程与后台线程的对比”这一节。线程池线程总是后台线程,如果CLR决定终止进程它们就不会完成任务。
- 受计算限制的任务需要时间非常长;这样,我不会让线程池负担逻辑,因为它试图找出是否需要创建一个额外的线程。
- 我想开始线程并很可能用Thread.Abort方法来过早的结束它。(在第22章讨论,“CLR宿主和应用程序域”)
为了创建专有线程,你需要使用System.Threading.Thread类的构造函数,传递执行的方法名称到它的构造函数。以下是Thread的构造函数原型:
public sealed class Thread : CriticalFinalizerObject, ... { public Thread(ParameterizedThreadStart start); // Less commonly used constructors are not shown here }
Start方法的参数指定一个方法,它会被专用线程执行,这个方法必须和ParameterizedThreadStart委托的签名一样。(6)(不推荐使用ThreadStart委托)
delegate void ParameterizedThreadStart(Object obj);
构造一个Thread对象是一个相当轻量的操作,因为它不实际需要创建一个操作系统物理线程。要真实创建一个操作系统物理线程并让它执行返回方法,你必须调用Thread.Start方法,传递object(state)来作为回调方法的参数。以下代码演示了如何创建专用线程并异步执行:
using System; using System.Threading; public static class Program { public static void Main() { Console.WriteLine("Main thread: starting a dedicated thread " + "to do an asynchronous operation"); Thread dedicatedThread = new Thread(ComputeBoundOp); dedicatedThread.Start(5); Console.WriteLine("Main thread: Doing other work here..."); Thread.Sleep(10000); // Simulating other work (10 seconds) dedicatedThread.Join(); // Wait for thread to terminate Console.WriteLine("Hit <Enter> to end this program..."); Console.ReadLine(); } // This method's signature must match the ParameterizedThreadStart delegate private static void ComputeBoundOp(Object state) { // This method is executed by a dedicated thread Console.WriteLine("In ComputeBoundOp: state={0}", state); Thread.Sleep(1000); // Simulates other work (1 second) // When this method returns, the dedicated thread dies } }
当我编译并运行这段代码时,我得到如下输出:
Main thread: starting a dedicated thread to do an asynchronous operation
Main thread: Doing other work here...
In ComputeBoundOp: state=5
有时当我运行这段代码时,会得到以下输出,因为我无法控制Windows如何调动这两个线程:
Main thread: starting a dedicated thread to do an asynchronous operation
In ComputeBoundOp: state=5
Main thread: Doing other work here...
注意我在Main函数中调用了Join方法。Join方法导致调用线程停止运行任何代码直到用dedicatedThread标示的线程销毁自己或被终止。
使用线程的原因
使用线程的原因有3个:
- 你可以使用线程来隔离代码。这可以提高程序可靠性,实际上,这就是为什么Windows在操作系统中引入线程的概念。Windows为了可靠性而需要线程,因为你的程序对操作系统来讲,是一个第三方组件,微软不确定在发布之前你的代码质量如何。然而,你可以在发布之前测试,既然你测试整个程序,你应该了解它们是健壮且高质量的。这样来讲,你的程序需要的健壮性并不像操作系统那么高,因此,你的程序不需要使用线程来提高健壮性。如果你的程序支持加载由别人开发的组件,你的程序需要更加健壮,并且使用线程可以满足这个需求。
- 你可以使用线程来让你的代码更简洁。有时用它自己的线程来执行任务是很简单的事情。当然,你这样做的时候,你正在使用额外资源,并没有有效的编写代码。现在,计算在一些重要资源上我也可以编写简洁代码。如果我不这样做,我仍然会一直写机器语言而不是成为C#开发者。但是有时我看见人们使用线程来选择一个更容易的编程技术,实际上,他们本质上让他们的生活(和代码)变得更复杂了。通常,当你引进线程时,你同样带来需要线程同步结构来决定什么时候其他线程结束。一旦你这样处理,你就在使用更多的资源,并且让你的代码更难懂。所以在开始使用线程之前,请确保它们真正对你有帮助。
- 你可以使用线程来同时处理。如果,仅仅如果你知道你的程序是运行在一个多处理器中,你可以并发执行任务来得到性能提升。如今,多处理器系统非常普遍,所以让你的程序支持多处理器是有意义的,你可以参见第26和第27章。
现在,我会和你分享一下我的观点。每个计算机都有一个难以置信的强大资源在里面:CPU自己。如果有人花钱在计算机上,计算机会一直运行着。换句话说,我相信CPU应该一直达到{bfb}的利用率。我会给你两个告诫。{dy},如果计算机使用电池,你不会想让CPU一直{bfb}运转,因为这样会很快把电耗光。第二,一些数据中心更喜欢有10台机器有50%的CPU利用率而不是5台机器{bfb}的CPU利用率,因为正在全速运行的CPU往往会产生极大的热量,而这需要冷却系统,像HVAC(供电空调)这样的冷却系统的成本要高于多台计算机供电能力,降低运行。虽然数据中心发现管理多台机器的成本越来越高,因为每台机器不得不定期升级硬件和软件还有显示器,但是这样比运行冷却系统要花费少。
现在,如果你同意我的观点。那么下一步就是想出CPU应该做什么。在我给你我的思路前,让我先告诉你一些事情。在过去,开发者和用户总是觉得计算机不够强大。因此,我们开发者不会仅仅执行代码除非用户给我们权限并指定对程序来讲,通过UI控件比如菜单、按钮、复选框来消耗CPU资源是OK的。
但是现在,一切都变了。计算机发展带来庞大的计算能力,甚至更强大的计算能力会出现在不远的将来。在这章开始时,我向你展示了任务管理器报告我的CPU那时占用0%。如果我的计算机是一个四核而不是双核,任务管理器会更经常报告0%。当80核浮出水面,机器一直会像没有做任何事情。对计算机采购人来说,这好像他们在花费更多的金钱来买更强大的CPU,计算机却做更少的工作。
这就是为什么计算机制造商很难卖给用户多核计算机:软件很难利用硬件资源,用户也从更多的CPU中得不到好处。我想说的是,现在我们有大量的计算能力,而且越来越多,因此开发者可以积极消费。是的,过去,我们很难想象让我们的程序执行一些计算除非我们知道用户想要计算的结果。但是现在,我们有额外的计算能力,我们可以做梦了。
这有个例子:当你停止在Visual Studio编辑器编辑时,Visual Studio自动产生编译并编译你的代码。这让开发者难以置信的高产,因为它们能边打字边在源文件中看到警告的错误,这样可以很快的解决它们。实际上,开发者今天的编辑-编译-调试循环会变成-编辑-调试循环,因为编译代码一直在发生。你,作为一个用户,不会关心这,因为有很多计算能力可用,你正在做的其他事情受到编译器频繁执行编译的影响微乎其微。实际上,我期盼着,在未来的Visual Studio版本,编译菜单会xx消失,因为这一切都自动化了。不仅仅程序的UI更简单了,程序给用户提供的“答案”,让他们更高产了。
当我们移除像菜单这样的UI组件时,计算机变得更简洁了。这会有更少的选项,更少的概念让他们去读和理解。这就是多核革命,允许我们去移除这些UI项,因此让软件对用户来讲更简洁,使得我的祖母某{yt}会感觉使用计算机很舒服。对开发者来讲,移除UI项通常会带来更少的测试量,同时对用户的基本代码会提供更少的选项。如果你现在本地化UI项文字和你的文档(类似微软),移除UI项意味着你可以书写更少的文档,你不必本地化这些文档。所有这些都会给你的组织节省时间和金钱。
这有一些敢作敢为的CPU消耗:拼写检查和语法检查,重新计算电子表格,索引你的磁盘文件以加速搜索,整理你的硬盘以提高IO性能。
我想生活在一个UI减少并简洁的世界,我会有更多的可视面积去显示我正在工作的数据,程序提供给我信息,帮助我更快更有效的完成工作,而不是我告诉程序去为我得到信息。我想近几年已经有这样的硬件来给开发者使用了。这正是软件创造性的利用硬件的时机。