Task是.NET Framework 3.0出现的,线程是基于线程池的,而后提供了丰富的API。Task被称之为多线程的最佳实践。html
首先咱们来看下如何使用Task来启动线程:数据库
/// <summary> /// 一个比较耗时耗资源的私有方法 /// </summary> private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
/// <summary> /// Task是.NET Framework 3.0出现的,线程是基于线程池的,而后提供了丰富的API。 /// </summary> private void btnTask_Click(object sender, EventArgs e) { Console.WriteLine($"****************btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); { Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1")); task.Start(); } { Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2")); } { TaskFactory taskFactory = Task.Factory; Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3")); } Console.WriteLine($"****************btnTask_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); }
Task的线程是源于线程池,线程池是单例的,全局惟一的。编程
//Task的线程是源于线程池 //线程池是单例的,全局惟一的 { //设置的最大值,必须大于CPU核数,不然设置无效 //全局的,请不要这样设置!!!此处设置只是为了演示 ThreadPool.SetMaxThreads(12, 12); //设置后,同时并发的Task只有12个;并且线程是复用的; for (int i = 0; i < 100; i++) { int k = i; Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); }); } //假如说我想控制下Task的并发数量,该怎么作? }
注意:线程池的线程数量,设置的最大值,必须大于CPU核数,不然设置无效。缓存
运行结果以下:多线程
从结果中能够看出同时并发的Task只有12个(线程ID从03到14);并且线程是复用的;并发
同步等待:less
{ Stopwatch stopwatch = new Stopwatch(); //计时 stopwatch.Start(); Console.WriteLine("在Sleep以前"); Thread.Sleep(2000);//同步等待--当前线程等待2s 而后继续 Console.WriteLine("在Sleep以后"); stopwatch.Stop(); Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}"); }
异步等待(Task.Delay方法,通常结合ContinueWith一块儿用),以下所示:异步
{ Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Delay以前"); Task task = Task.Delay(2000) //异步等待--等待2s后启动新任务 .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); Console.WriteLine("在Delay以后"); //stopwatch.Stop(); //Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); }
何时能用多线程? 任务能并发的时候。多线程能干吗?提高速度/优化用户体验。分布式
ContinueWhenAny:同时并发多个任务,当其中任意一个任务完成的时候,也多是多个任务同时第一时间完成的时候,执行后续的任务。非阻塞式的回调。性能
ContinueWhenAll:同时并发多个任务,当全部的任务都完成的时候,执行后续的任务。非阻塞式的回调。
WaitAny:阻塞当前线程,等着任意一个任务完成。
WaitAll:阻塞当前线程,等着所有任务完成。
/// <summary> /// 模拟讲课 /// </summary> private void Teach(string lesson) { Console.WriteLine($"{lesson}开始讲。。。"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Console.WriteLine($"{lesson}讲完了。。。"); } /// <summary> /// 模拟编程过程 /// </summary> private void Coding(string name, string projectName) { Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
{ //何时能用多线程? 任务能并发的时候 //多线程能干吗?提高速度/优化用户体验 Console.WriteLine("Eleven开启了一学期的课程"); this.Teach("Lesson1"); this.Teach("Lesson2"); this.Teach("Lesson3"); //不能并发,由于有严格顺序(只有Eleven讲课) Console.WriteLine("部署一下项目实战做业,须要多人合做完成"); //开发能够多人合做---多线程--提高性能 TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => this.Coding("张三", "Portal"))); taskList.Add(taskFactory.StartNew(() => this.Coding("李四", "DBA "))); taskList.Add(taskFactory.StartNew(() => this.Coding("王五", "Client"))); taskList.Add(taskFactory.StartNew(() => this.Coding("赵六", "BackService"))); taskList.Add(taskFactory.StartNew(() => this.Coding("钱七", "Wechat"))); //谁第一个完成,获取一个红包奖励 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); //实战做业完成后,一块儿庆祝一下 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一块儿庆祝一下" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}"))); //ContinueWhenAny ContinueWhenAll 非阻塞式的回调;并且使用的线程多是新线程,也多是刚完成任务的线程,惟一不多是主线程。 //阻塞当前线程,等着任意一个任务完成 Task.WaitAny(taskList.ToArray());//也能够限时等待 Console.WriteLine("Eleven准备环境开始部署"); //须要可以等待所有线程完成任务再继续 阻塞当前线程,等着所有任务完成 Task.WaitAll(taskList.ToArray()); Console.WriteLine("5个模块所有完成后,Eleven集中点评"); //Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操做 //阻塞卡界面,是为了并发以及顺序控制 //网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,须要等待WaitAll //列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪一个先完成就用哪一个结果,其余的就无论了 }
启动带有回调和带有返回值的线程:
{ //带有回调 Task.Run(() => this.DoSomethingLong("btnTask_Click")).ContinueWith(t => Console.WriteLine($"btnTask_Click已完成" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); } { //带有返回值 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); int i = result.Result;//会阻塞 }
控制Task的并发数量,该怎么作?(下文会介绍更好的解决方案)
{ //假如说我想控制下Task的并发数量,该怎么作? 20个 List<Task> taskList = new List<Task>(); for (int i = 0; i < 10000; i++) { int k = i; if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20) { Task.WaitAny(taskList.ToArray()); taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); } taskList.Add(Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); })); } }
想使用返回值但又不想阻塞线程怎么办?
//想使用返回值但又不想阻塞线程怎么办? { Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }).ContinueWith(tInt => { int i = tInt.Result; //不会阻塞 }); //不会阻塞 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); Task.Run(() => { int i = result.Result; }); }
若是在启动线程的时候想带一些参数信息,则可使用对应的重载方法:
{ TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(o => this.Coding("张三", "Portal"), "张三")); taskList.Add(taskFactory.StartNew(o => this.Coding("李四", " DBA "), "李四")); taskList.Add(taskFactory.StartNew(o => this.Coding("王五", "Client"), "王五")); taskList.Add(taskFactory.StartNew(o => this.Coding(" 赵六", "BackService"), " 赵六")); taskList.Add(taskFactory.StartNew(o => this.Coding("钱七", "Wechat"), "钱七")); //谁第一个完成,获取一个红包奖励 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); }
几乎90%以上的多线程场景,以及顺序控制,以上的Task的方法就能够完成。
若是你的多线程场景太复杂搞不定,那么请梳理一下你的流程,简化一下。
建议最好不要线程嵌套线程,两层勉强能懂,三层hold不住的,更多只能求神。
Parallel能够启动多个线程去并发执行多个Action,它是多线程的。Parallel最直观的特色是主线程(当前线程)也会参与计算---阻塞界面(主线程忙于计算,无暇他顾)。
Parallel是在Task的基础上作了一个封装,它的效果等于TaskWaitAll+主线程(当前线程)参与计算。
/// <summary> /// Parallel /// </summary> private void btnParallel_Click(object sender, EventArgs e) { Console.WriteLine($"****************btnParallel_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); { //Parallel并发执行多个Action 多线程的 //主线程(当前线程)会参与计算---阻塞界面 //等于TaskWaitAll+主线程计算 Parallel.Invoke( () => this.DoSomethingLong("btnParallel_Click_1"), () => this.DoSomethingLong("btnParallel_Click_2"), () => this.DoSomethingLong("btnParallel_Click_3"), () => this.DoSomethingLong("btnParallel_Click_4"), () => this.DoSomethingLong("btnParallel_Click_5")); } Console.WriteLine($"****************btnParallel_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); }
运行结果以下:
能够看出线程ID为01的主线程(当前线程)也参与计算了。
Parallel其余的API以下所示:
//主线程(当前线程)会参与计算 { Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } //主线程(当前线程)会参与计算 { Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}")); }
如何控制并发(线程)数量?上面咱们在介绍Task的时候已经写了一种解决方案,其实还有一种更好的解决方案,就是使用Parallel来实现。
//控制线程数量(并发数量) //会阻塞当前线程(主线程)-- 卡界面 { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } //控制线程数量(并发数量) //不会阻塞当前线程(主线程) { Task.Run(() => { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); }); }
Demo源码:
连接:https://pan.baidu.com/s/1SpbyPnohojyakxCOu4S9DA 提取码:mwd1
此文由博主精心撰写转载请保留此原文连接:https://www.cnblogs.com/xyh9039/p/13556424.html