C# Task 篇幅一
背景
ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:
- ThreadPool不支持线程的取消、完成、失败通知等交互性操作
- ThreadPool不支持线程执行的先后次序
以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在.netFramwork3.0出现的Task,线程是基于线程池,然后提供了丰富的API
下面我们来初步认识一下Task,下面我们先新增一个公共的方法以下方法的演示中都会用到:
#region Private Method /// <summary> /// 一个比较耗时耗资源的私有方法 /// </summary> /// <param name="name"></param> 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 < 1000000000; i++) { lResult += i; } Console.WriteLine($"****DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}****"); } #endregion
一:Task启动任务的几种方式
{ 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.Factory等同于: new TaskFactory() Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3")); }
二:Task.Delay()和Thread.Sleep()方法的比较运用
private void Test() { Console.WriteLine($"****************btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Sleep之前"); Thread.Sleep(2000); //同步等待--当前线程等待2s 然后继续 Console.WriteLine("在Sleep之后"); stopwatch.Stop(); Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}"); } { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Delay之前"); Task task = Task.Delay(2000) .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); });//异步等待--等待2s后启动新任务 Console.WriteLine("在Delay之后"); } Console.WriteLine($"****************btnTask_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); }
通过执行结果如下:
通过观察发现:
- Sleep是同步执行方法,即是遇到Sleep先等待,然后才能接着做其它的
- Delay是异步执行方法,一般不会单独使用,而是会跟ContinueWith等一起联合使用,即是重新开启一个线程,这个线程多少时间之后执行!
三:TaskFactory类的ContinueWhenAny 和 ContinueWhenAll
/// <summary> /// 模拟Coding过程 /// </summary> /// <param name="name"></param> /// <param name="projectName"></param> 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 < 1000000000; 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}***"); } private void Test() { Console.WriteLine($"***btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***"); TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(o => this.Coding("AA", "Portal"), "AA")); taskList.Add(taskFactory.StartNew(o => this.Coding("BB", " DBA "), "BB")); taskList.Add(taskFactory.StartNew(o => this.Coding("CC", "Client"), "CC")); taskList.Add(taskFactory.StartNew(o => this.Coding("DD", "BackService"), " DD")); taskList.Add(taskFactory.StartNew(o => this.Coding("EE", "Wechat"), "EE")); //谁第一个完成,获取一个红包奖励 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}"))); //ContinueWhenAny ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程 Console.WriteLine($"***btnTask_Click end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***"); }
通过结果我们总结如下:
- ContinueWhenAny:等待任意一条完成
- ContinueWhenAll :等待所有的完成
- ContinueWhenAny 和 ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程
四:Task中的 WaitAny 和 WaitAll
/// <summary> /// 模拟Coding过程 /// </summary> /// <param name="name"></param> /// <param name="projectName"></param> 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 < 1000000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"***Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***"); } private void Test() { Console.WriteLine($"***btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***"); TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(o => this.Coding("AA", "Portal"), "AA")); taskList.Add(taskFactory.StartNew(o => this.Coding("BB", " DBA "), "BB")); taskList.Add(taskFactory.StartNew(o => this.Coding("CC", "Client"), "CC")); taskList.Add(taskFactory.StartNew(o => this.Coding("DD", "BackService"), " DD")); taskList.Add(taskFactory.StartNew(o => this.Coding("EE", "Wechat"), "EE")); //阻塞当前线程,等着任意一个任务完成 Task.WaitAny(taskList.ToArray());//也可以限时等待,如果是winform界面会卡 Console.WriteLine("第一个模块已经完成,现在开始准备环境开始部署"); //需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成,如果是winform界面会卡 Task.WaitAll(taskList.ToArray()); Console.WriteLine("5个模块全部完成,准备联测"); Console.WriteLine($"***btnTask_Click end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***"); }
运行执行如下:
通过上面得到如下:
- Task.WaitAny:等待任意一条完成。例如:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了
- Task.WaitAll :等待所有的完成。例如:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll
- Task.WaitAny 和Task.WaitAll都是阻塞当前线程,等任务完成后执行操作, 阻塞卡界面,是为了并发以及顺序控制
五:Task想要控制先后顺序,可以通过ContinueWith
这个在什么的Delay中已经看到了,可以一直使用ContinueWith来增加任务
1 | Task.Run(() => this.DoSomethingLong( "btnTask_Click" )).ContinueWith(t => Console.WriteLine($ "btnTask_Click已完成{Thread.CurrentThread.ManagedThreadId.ToString(" 00 ")}" )); // 回调 |
六:任务想要有返回值,并且把返回值传递到ContinueWith中
,可以通过如下代码实现:
Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }).ContinueWith(tInt => { int i = tInt.Result; //有堵塞 });
注意:使用.Result这个会堵塞线程
七:Task的线程是源于线程池,线程池是单例的,全局唯一
我们可以通过下面代码来测试一下:
ThreadPool.SetMaxThreads(8, 8); 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); }); }
设置线程池最大线程为8个后,我们通过监测结果发现:同时并发的Task只有8个,而且线程Id是重复出现的,即线程是复用的。所以线程池是是全局的,以后要慎重设置线程池数量。
八:Parallel
是来源于命名空间为:System.Threading.Tasks,并发执行多个Action 多线程。主线程会参与计算---阻塞界面,等于TaskWaitAll+主线程计算
具体的一些用法如下:
{ //启动5个任务 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")); } { //启动5个任务,i是从0-4 Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } { //启动5个任务 Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}")); }
上面三种方法都是可以的。然后Parallel是线程阻塞,那我们可不可以不堵塞线程呢,这个我们可以重启一个任务,让子线程去做这个事情,如下:
Task.Run(() => { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); });
这是一种思路,以后会有很多地方用到。
九:控制线程数量
上面的Task和TaskFactory以及Parallel都已经学习完了,我们晓的线程是根据电脑资源有关系的,有时候批量启动N个线程,效率还不如单线程高,因为会有线程切换是要耗时的,那我们怎么控制保证一次调用多个线程呢,下面提供Task和Parallel的两种解决方案,具体如下:
{ //Parallel设置最大线程为3 ParallelOptions options = new ParallelOptions(); //设置最大线程为3,以后Parallel想要控制线程数量只需要设置ParallelOptions类中的MaxDegreeOfParallelism即可 options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } { //Task设置最大线程为3 List<Task> taskList = new List<Task>(); for (int i = 0; i < 10000; i++) { int k = i; if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 3) { 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); })); } }
学完上面的异步方法,Thread和Task,有人会提出以下问题:
1:什么时候能用多线程? 任务能并发的时候能够使用多线程
2:多线程能干嘛?多线程能够提升速度/优化用户体验,以cpu资源来换时间
以上内容来源:胖胖滴加菲猫 https://www.cnblogs.com/loverwangshan/p/10444773.html
付费内容,请联系本人QQ:1002453261
本文来自博客园,作者:明志德道,转载请注明原文链接:https://www.cnblogs.com/for-easy-fast/articles/12447952.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析