Task基础-创建Task,Task传参,获取Task返回值
- Task基础介绍
- Task的创建
- 获取Task的执行结果
- 补充细节
1、Task基础介绍
Task类是Task Programming Library(TPL)中最核心的一个类,下面我将会像大家展示如何使用一些方法来创建不同类型的Task,取消Task,等待Task执行完成,获取Task执行后的结果和对异常进行处理。
我们用静态方法:Task.Factory.StartNew()来创建了一个最简单的Task--在屏幕上打印一句话。这段代码确实简单,而且都没有任何输入和需要返回的结果。
Task.Factory.StartNew(() => { Console.WriteLine("Hello World"); });
2、Task的创建
为了执行一个简单的Task,一般进行以下步骤:
第一步,创建一个Task类的实例
第二步,传入一个Action委托,这个委托中的方法就是这个Task运行时要执行的方法,而这个委托必须作为Task构造函数的一个参数传入。传入委托作为参数的时候有多种方法:传入匿名委托,lambda表达式等。
第三步,调用Task实力的start()方法来运行。
当这个Task实例开始运行的时候,它就被传给了内部的一个task scheduler,这个scheduler负责把我们创建的task交给底下的线程去执行。
下面就看看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Task创建 { internal class Program { static void Main(string[] args) { //第一种,使用委托和方法 Task task1 = new Task(new Action(printMsg)); //第二种,使用匿名委托 Task task2 = new Task(delegate { printMsg (); }); //第三种,使用lambda和方法 Task task3 = new Task(()=>printMsg ()); //第四种,使用lambda和匿名方法 Task task4 = new Task(()=> { printMsg(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start();
//第四种,使用Task.Run(),Task.Run()的返回值也是一个Task Task.Run(()=> { printMsg(); });
Console.Read(); } static void printMsg() { Console.WriteLine("hello world"); } } }
上面代码创建Task的方法和我们之前的第一段代码的创建Task的方法不同。在之前我们采用的是Task.Factory.StartNew()方法来创建的,这个方法创建Task并且开始运行Task,其实两端代码的结果是一样的,这里给出一点建议:如果这是想简单的创建一个Task,那么使用Factory.NewStart()来创建,很简便,如果像对所创建的Task附加更多的定制和设置特定的属性,那么还是得一步一步的按照我们说的那些步骤来。
3、获取Task的执行结果
在创建Task的时候,我们在构造函数中传入了一个System.Action的委托,如果我们想要把一些参数传入到Task中,那么我们可以Task的另一个构造函数public Task(Action<object> action, object state),传入System.Action<object>的委托,其中的那个object就是我们传入的参数。举例如下:
using System; using System.Threading.Tasks; namespace Task传入参数 { internal class Program { static void Main(string[] args) { string[] messages = { "First task", "Second task","Third task", "Fourth task" }; foreach (var item in messages) { //第一种方法 Task task = new Task((s) => printMsg((string)s), item); //第二种方法 //Task task = new Task(()=>printMsg(item)); task.Start(); } Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); } static void printMsg(string msg) { Console.WriteLine($"print message:{msg}"); } } }
注意:我们在传入参数后,必须把参数转换为它们原来的类型,然后再去调用相应的方法。例子中,因为System.Action对应的方法是printMessage()方法,而这个方法的要求的参数类型是string,所以要转换为string。
两种传入参数的方法有什么区别?目前我还不太清楚,肯是因为第二种方法会导致闭包。
想向Task传入参素,只能用System.Action<object>
3、获取Task的执行结果
如果要获取Task的结果,那么在创建Task的时候,就要采用Task<T>来实例化一个Task,其中的那个T就是task执行完成之后返回结果的类型。之后采用Task实例的Result属性就可以获取结果,只有在task执行完成之后,才能获取到Result的值。
代码显示如下:
1、使用Task开启一个线程,计算10以内求和,并获得返回值
using System; using System.Threading.Tasks; using System.Threading; namespace Task获得返回值 { internal class Program { static void Main(string[] args) { Task<int> task = new Task<int>(() => getSum()); task.Start(); Console.WriteLine($"task result is {task.Result}"); Console.ReadLine(); } static int getSum() { int sum = 0; for (int i = 0; i < 10; i++) { sum = sum + i; Thread.Sleep(100); } return sum; } } }
2、对上面的代码进行修改,计算任意数字内的求和,需要输入一个参数,并获得返回值。
using System; using System.Threading.Tasks; using System.Threading; namespace Task获得返回值 { internal class Program { static void Main(string[] args) { Task<int> task = new Task<int>((l) => getSum((int)l),10); task.Start(); Console.WriteLine($"task result is {task.Result}"); Console.ReadLine(); } static int getSum(int len) { int sum = 0; for (int i = 0; i < len; i++) { sum = sum + i; Thread.Sleep(100); } return sum; } } }
3、通过Task.Factory.StartNew<TResualt>创建一个Task,并获得结果。
using System; using System.Threading.Tasks; namespace TaskFactory获得返回值 { internal class Program { static void Main(string[] args) { Task<int> task = Task.Factory.StartNew<int>((n) => { int sum = 0; for (int i = 0; i < (int)n; i++) { sum = sum + i; } return sum; }, 100); Console.WriteLine($"sum = {task.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。