.NET 4 并行(多核)编程系列之二 从Task开始
转载:http://www.cnblogs.com/yanyangtian/archive/2010/05/22/1741379.html
1.Task基础介绍
首先我们还是来看看一段简单的代码:
这里展示的只是一段简单的代码,不能显示出并行编程的特点。但是我们还是从最基本的开始看,慢慢进入深一点的话题。
如果你曾经用过.NET 中的多线程编程,比较一下,就会发现:这段代码虽然在底层还是使用了多线程,但是写法上却简化了很多,一行代码就实现了一个并行编程。
下面我们就从Task类开始谈。
Task类是Task Programming Library(TPL)中最核心的一个类,下面我将会像大家展示如何使用一些方法来创建不同类型的Task,
取消Task,等待Task执行完成,获取Task执行后的结果和对异常进行处理。
在开始讨论之前,我们首先快速的看看之前的代码:
这个命名空间将会是我们之后在讲述并行编程经常使用的一个。这个空间包含了很多与并行编程有关的类。
还有一个要你使用的命名空间是:System.Threading,大家对这个应该比较熟悉了,之前的多线程编程常常使用到,这个空间下包含了一些在并行编程中用来协调数据的一些类。
上面代码中,最主要的代码如下:
1 2 3 4 | Task.Factory.StartNew(() => { Console.WriteLine( "Hello World" ); }); |
我们用静态方法:Task.Factory.StartNew()来创建了一个最简单的Task--在屏幕上打印一句话。这段代码确实简单,而且都没有任何输入和需要返回的结果。
下面我们就正式进入议题:
2.Task的创建
如果只是创建一个简单的Task,我们只要为该Task提供一个执行体就行了,执行体可以是一个委托delegate或者action。我们之前展示的那段代码就是采用了lambda表达式来作为Task的执行体。
2.1 创建一个简单的Task
为了执行一个简单的Task,一般进行以下步骤:
首先,要创建一个Task类的实例,
然后,传入一个System.Action委托,这个委托中的方法就是这个Task运行时你要执行的方法,而且这个委托必须作为Task构造函数的一个参数传入。我们在传入委托作为参数的时候有多种方式:传入匿名委托,
Lambda表达式或者一个显示什么方法的委托。
最后,调用Task实例的Start()方法来运行。
当这个Task实例开始运行的时候,它就被传给了内部的一个task scheduler,这个scheduler负责把我们创建的task交给底下的线程去执行。
下面就看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | using System; using System.Threading.Tasks; namespace Listing_02 { class Listing_02 { static void Main( string [] args) { // use an Action delegate and a named method Task task1 = new Task( new Action(printMessage)); // use a anonymous delegate Task task2 = new Task( delegate { printMessage(); }); // use a lambda expression and a named method Task task3 = new Task(() => printMessage()); // use a lambda expression and an anonymous method Task task4 = new Task(() => { printMessage(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start(); // wait for input before exiting Console.WriteLine( "Main method complete. Press enter to finish." ); Console.ReadLine(); } static void printMessage() { Console.WriteLine( "Hello World" ); } } } |
不知道大家注意到了没有,上面代码创建Task的方法和我们之前的第一段代码的创建Task的方法不同。在之前我们采用的是Task.Factory.StartNew()方法来创建的,这个方法创建Task并且开始运行Task,其实两端代码的结果是一样的,这里给出一点建议:如果这是想简单的创建一个Task,那么使用Factory.NewStart()来创建,很简便,如果像对所创建的Task附加更多的定制和设置特定的属性,那么还是得一步一步的按照我们说的那些步骤来。(详细的我们后续会介绍的)
2.1 为创建的Task传入参数
我们之前提过,在创建Task的时候,我们在构造函数中传入了一个System.Action的委托,如果我们想要把一些参数传入到Task中,那么我 们可以传入System.Action<object>的委托,其中的那个object就是我们传入的参数。还是给大家举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System; using System.Threading.Tasks; namespace Listing_04 { class Listing_04 { static void Main( string [] args) { string [] messages = { "First task" , "Second task" , "Third task" , "Fourth task" }; foreach ( string msg in messages) { Task myTask = new Task(obj => printMessage(( string )obj), msg); myTask.Start(); } // wait for input before exiting Console.WriteLine( "Main method complete. Press enter to finish." ); Console.ReadLine(); } static void printMessage( string message) { Console.WriteLine( "Message: {0}" , message); } } } |
注意:我们在传入参数后,必须把参数转换为它们原来的类型,然后再去调用相应的方法。例子中,因为System.Action对应的方法是printMessage()方法,而这个方法的要求的参数类型是string,所以要转换为string。
想向Task传入参素,只能用System.Action<object>
3.获取Task的执行结果
如果要获取Task的结果,那么在创建Task的时候,就要采用Task<T>来实例化一个Task,其中的那个T就是task执行完成之后返回结果的类型。之后采用Task实例的Result属性就可以获取结果。
代码显示如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static void Main( string [] args) { // create the task Task< int > task1 = new Task< int >(() => { int sum = 0; for ( int i = 0; i < 100; i++) { sum += i; } return sum; }); task1.Start(); // write out the result Console.WriteLine( "Result 1: {0}" , task1.Result); Console.ReadLine(); } |
只有在task执行完成之后,才能获取到Result的值。
下面的代码展示了如何通过Task.Factory.StartNew<T>()创建一个Task,并且获取结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | static void Main( string [] args) { // create the task Task< int > task1 = Task.Factory.StartNew< int >(() => { int sum = 0; for ( int i = 0; i < 100; i++) { sum += i; } return sum; }); // write out the result Console.WriteLine( "Result 1: {0}" , task1.Result); Console.ReadLine(); } |
4. 补充细节
在创建Task的时候,Task有很多的构造函数的重载,一个主要的重载就是传入TaskCreateOptions的枚举:
TaskCreateOptions.None:用默认的方式创建一个Task
TaskCreateOptions.PreferFairness:请求scheduler尽量公平的执行Task(后续文章会将是,Task和线程一样,有优先级的)
TaskCreateOptions.LongRunning:声明Task将会长时间的运行。
TaskCreateOptions.AttachToParent:因为Task是可以嵌套的,所以这个枚举就是把一个子task附加到一个父task中。
最后要提到的一点就是,我们可以在Task的执行体中用Task.CurrentId来返回Task的唯一表示ID(int)。如果在Task执行体外使用这个属性就会得到null。
附上自己实现的这一期的所有源代码:https://files.cnblogs.com/winds/Task.zip
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?