上一篇文章(C# Thread类 – 多线程学习1)说过多线程之Thread 类,下面继续学习多线程的另一个更重要的知识点:Task 类,如果有时间也建议看下上面的这篇文章,或许有用呐。
Thread线程是用来创建并发的一种低级别工具,它具有一些限制,尤其是:
- 虽然开始线程的时候可以方便的传入数据,但是当join的时候很难从线程获得返回值。
- 可能需要设置一些共享字段。
- 如果操作抛出异常,铺货和传播该异常都很麻烦
- 无法告诉线程在结束时开始另外的工作,你必须进行join操作(在进程中阻塞当前的线程)
- 很难使用较小的并发(concurrent)来组件大型的并发
Task类可以很好的解决上述问题,它是一个高级抽象:它代表了一个并发操作(concurrent),该操作可能有Thread支持,或不由Thread支持。
- Task是可组合的(可使用continuation把他们穿成链)。
- Tasks可以使用线程池来减少启动延迟。
- 使用TaskCompletionSource,Tasks可以利用回调的方式,在等待I/O绑定操作时完全避免使用线程。
Task建立多线程有多种方法,下面通过代码进行说明。代码可能有点长,但是把它分块就比较好理解的,因为是多种方法合并在一起进行介绍的,所以整个代码与输出结果比较长。
直接上代码,后面有对代码的解析:
using System; using System.Threading; using System.Threading.Tasks; namespace ch_Task { class Program { static void CallTask2(string name) { Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:" + name); for (int i = 1; i <= 5; i++) { Thread.Sleep(500); Console.WriteLine($"{i.ToString()} ---name:task2"); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:" + name); } public static void CallTask3() { Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task3"); for (int i = 1; i <= 5; i++) { Thread.Sleep(500); Console.WriteLine($"{i.ToString()} ---name:task3"); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task3"); } public static void CallTask3Continue(Task t) { Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task3.continue"); for (int i = 1; i <= 5; i++) { Thread.Sleep(500); Console.WriteLine($"{i.ToString()} ---name:task3.continue"); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task3.continue"); } async static void task4() { Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task4"); for (int i = 1; i<= 5; i++) { Console.WriteLine($"{i.ToString()} ---name:task4"); await Task.Delay(500); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task4"); } static void Main(string[] args) { //-----------方式1----------------- Task task1 = new Task(() => { Console.WriteLine($"taskID:{Task.CurrentId} beginning! ------name:task1"); for(int i=1;i<=5;i++) { Thread.Sleep(500); Console.WriteLine($"{i.ToString()} ---name:task1"); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ------name:task1"); }); task1.Start(); // program execution after task1 finish task1.ContinueWith((task) => { Console.WriteLine($"taskID:{task.Id}.continue begin and this task id is {Task.CurrentId}"); for (int i = 1; i <= 5; i++) { Thread.Sleep(500); Console.WriteLine($"{i.ToString()} ---name:task1.continue"); } Console.WriteLine($"taskID:{Task.CurrentId} finished! ------name:task{task.Id}.continue"); }); //-------------方式2---------------- var task2 = new Task(() => CallTask2("Task2")); task2.Start(); //------------方式3------------ Task task3 = new Task(CallTask3); task3.Start(); Task task3Continue = task3.ContinueWith(CallTask3Continue); task3Continue.Wait(); // 阻塞主线程,直到task3Continue 线程执行结束 //-------------方式4------------- task4(); Console.ReadKey(); } } }
输出结果为:
taskID:1 beginning! ------name:task1 taskID:3 beginning! ---name:Task2 taskID:4 beginning! ---name:task3 1 ---name:task1 1 ---name:task2 1 ---name:task3 2 ---name:task1 2 ---name:task2 2 ---name:task3 3 ---name:task1 3 ---name:task2 3 ---name:task3 4 ---name:task1 4 ---name:task3 4 ---name:task2 5 ---name:task1 taskID:1 finished! ------name:task1 taskID:1.continue begin and this task id is 2 5 ---name:task3 5 ---name:task2 taskID:4 finished! ---name:task3 taskID:3 finished! ---name:Task2 taskID:5 beginning! ---name:task3.continue 1 ---name:task1.continue 1 ---name:task3.continue 2 ---name:task1.continue 2 ---name:task3.continue 3 ---name:task3.continue 3 ---name:task1.continue 4 ---name:task3.continue 4 ---name:task1.continue 5 ---name:task3.continue 5 ---name:task1.continue taskID:5 finished! ---name:task3.continue taskID:2 finished! ------name:task1.continue taskID: beginning! ---name:task4 1 ---name:task4 2 ---name:task4 3 ---name:task4 4 ---name:task4 5 ---name:task4 taskID: finished! ---name:task4
下面对输出结果进行分析:
直接看主函数部分,可以看到共有task1、task1.continue、task2、task3、task3.continue、task4六个子线程、分别用了四种方法:
task1 使用 : Task task1 = new Task(() =>{ } );直接去创建子线程。其实,这个运行的话可以简化即: 将下面代码: Task task1 = new Task(() =>{ } ); task1.start() 换成 : Task.Run(() =>{ } ); 即可
task2 使用: var task2 = new Task(() => CallTask2("Task2")) 创建子线程。
task3 使用: Task task3 = new Task(CallTask3);直接创建子线程
task4 使用异步方法去创建子线程,关键词:async, 可以看到在task4之前,使用了task3Continue.Wait(); 这个语句可以用来阻塞主线程,直到task3Continue 线程执行结束后才会 task4线程的定义与运行。 另外,可以看到,在别的task使用延时用的都是 Thread.Sleep(500); 而使用异步方法创建的子线程在使用延时 的时候使用的是 await Task.Delay(500);这一点需要留意。
task1.continue 线程 即 task1.ContinueWith((task) => {})在task1 完全执行结束后才执行, 同理 task3.continue在task3 完全结束之后才执行。 Task.ContinueWith((task) => {})可以将子线程串联起来