时间:2022-03-11 08:51:25 | 栏目:.NET代码 | 点击:次
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述多线程的发展历程【Thread,ThreadPool,Task,Parallel】,仅供学习分享使用,如有不足之处,还请指正。
Thread做为早期【.Net Framework1.0】的.Net提供的多线程方案,提供了很多的封装方法,来操作线程。具体如下所示:
通过Thread可以单独的开启一个线程,通过构造函数来创建线程对象,可以是无参数也可以是带参数。其中参数ThreadStart是一个无参数委托,ParameterizedThreadStart为一个带参数委托。
无参数,示例如下所示:
private void btnThread_Click(object sender, EventArgs e) { ThreadStart threadStart = new ThreadStart(DoSomethingLong); Thread thread = new Thread(threadStart); thread.Start(); } private void DoSomethingLong() { string name = "Thread"; Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU计算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
示例结果如下所示:
带参数示例,如下所示:
private void btnThread2_Click(object sender, EventArgs e) { ParameterizedThreadStart threadStart = new ParameterizedThreadStart(DoSomethingLongWithParam); Thread thread = new Thread(threadStart); string name = "Param"; thread.Start(name); } private void DoSomethingLongWithParam(object name) { Console.WriteLine("************DoSomethingLongWithParam 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU计算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLongWithParam 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
带参数示例结果,如下所示:
通过对以上示例进行分析,得出结论如下所示:
为了应对Thread创建缺乏管理的问题,在后续版本【.Net Framework2.0】中推出了线程池的概念。那什么是线程池呢?
池化资源管理设计思想:线程是一种资源,之前每次需要线程,都是去创建线程,使用完成后,再释放掉。池化,就是做一个容器,容器提前申请指定数量的线程,需要用到线程的时候,直接到线程池中取,用完之后再放回容器【通过控制状态标识线程是否正在被使用】。避免频繁的创建和销毁,容器还会根据限制的数量取申请和释放。
关于通过线程池创建多线程,具体示例如下:
private void btnThread3_Click(object sender, EventArgs e) { WaitCallback waitCallback = new WaitCallback(DoSomethingLongWithParam); string name = "ThreadPool"; ThreadPool.QueueUserWorkItem(waitCallback,name); }
线程池示例执行结果如下所示:
通过对线程池执行结果进行分析,得出结论如下:
随着.Net版本的演化,后续版本【.Net Framework3.0】推出了Task做为多线程解决方案。默认情况下,可以通过构造函数创建Task,示例如下:
private void btnTask_Click(object sender, EventArgs e) { Action action = new Action(DoSomethingLong); Task task = new Task(action); task.Start(); }
默认Task示例,执行结果如下:
通过对以上Task示例和源码进行分析,得出结论如下:
Parallel提供对并行线程的支持,可以通过Parallel同时发起多个线程,在某些方面具有应用优势,默认示例如下所示:
private void btnParallel_Click(object sender, EventArgs e) { Action action = new Action(DoSomethingLong); Parallel.Invoke(action,action,action); }
Parallel的Invoke方法执行,结果如下:
通过对Parallel的Invoke示例方法进行分析,得出结论如下:
以下面的一个场景为例进行说明:
假如开发一个系统,流程如下:
1. 前期的需求调研,需求分析,系统设计,详细设计(顺序执行,是开发编码的前提)
2.按模块开发【中间阶段,可多人同时工作】
3.测试【顺序执行,是开发编码的后续工作】
分析:以上三个阶段,每一个阶段又可以细分数个小阶段,其中有些阶段是顺序执行的,有些阶段又可以并行执行。
以代码的形式进行描述,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //开发前的工作 Console.WriteLine("组建团队"); Console.WriteLine("需求分析"); Console.WriteLine("系统设计"); Console.WriteLine("详细设计"); //开始开发 Task.Run(() => { Coding("张三", "接口"); }); Task.Run(() => { Coding("李四", "前端页面"); }); Task.Run(() => { Coding("王五", "手机App"); }); Task.Run(() => { Coding("刘大", "后端业务"); }); //开发后的工作 Console.WriteLine("alpha测试"); Console.WriteLine("beta测试"); Console.WriteLine("uat测试"); Console.WriteLine("系统上线"); } private void Coding(string developer,string model) { Console.WriteLine("【Begin】在{0},{1}开始开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer,model, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); Console.WriteLine("【 End 】在{0},{1}完成开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer, model, Thread.CurrentThread.ManagedThreadId); }
示例运行结果,如下所示:
通过分析以上示例,发现程序并未按照预期的运行,很明显的一点:测试跑到了开发前面。
为了解决上述顺序错乱的问题,Task提供了WaitAll方法,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //开发前的工作 Console.WriteLine("组建团队"); Console.WriteLine("需求分析"); Console.WriteLine("系统设计"); Console.WriteLine("详细设计"); //开始开发 List<Task> tasks = new List<Task>(); tasks.Add(Task.Run(() => { Coding("张三", "接口"); })); tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); })); tasks.Add(Task.Run(() => { Coding("王五", "手机App"); })); tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); })); Task.WaitAll(tasks.ToArray()); //开发后的工作 Console.WriteLine("alpha测试"); Console.WriteLine("beta测试"); Console.WriteLine("uat测试"); Console.WriteLine("系统上线"); }
运行示例,结果如下所示:
通过运行以上示例,发现:顺序确实符合预期,可以满足要求,但是程序会卡住,这点不太友好。
如何才能优雅的控制先后顺序呢?Task还提供了TaskFactory,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //开发前的工作 Console.WriteLine("组建团队"); Console.WriteLine("需求分析"); Console.WriteLine("系统设计"); Console.WriteLine("详细设计"); //开始开发 List<Task> tasks = new List<Task>(); tasks.Add(Task.Run(() => { Coding("张三", "接口"); })); tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); })); tasks.Add(Task.Run(() => { Coding("王五", "手机App"); })); tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); })); TaskFactory taskFactory = new TaskFactory(); taskFactory.ContinueWhenAll(tasks.ToArray(), new Action<Task[]>((taskArray) => { //开发后的工作 Console.WriteLine("alpha测试"); Console.WriteLine("beta测试"); Console.WriteLine("uat测试"); Console.WriteLine("系统上线"); })); }
TaskFactory示例测试,如下所示:
通过对示例进行分析,得出如下结论: