C# Task
在中我们有讲到委托的异步方法,thread,threadpool,然后今天来讲一下task,
threadpool相比thread来说具备了很多优势,但是threadpool却又存在一些使用上的不方便。比如:
- threadpool不支持线程的取消、完成、失败通知等交互性操作
- threadpool不支持线程执行的先后次序
以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在.netframwork3.0出现的task,线程是基于线程池,然后提供了丰富的api
下面我们来初步认识一下task,下面我们先新增一个公共的方法以下方法的演示中都会用到:
1 #region private method 2 /// <summary> 3 /// 一个比较耗时耗资源的私有方法 4 /// </summary> 5 /// <param name="name"></param> 6 private void dosomethinglong(string name) 7 { 8 console.writeline($"****dosomethinglong start {name} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}*****"); 9 long lresult = 0; 10 for (int i = 0; i < 1000000000; i++) 11 { 12 lresult += i; 13 } 14 console.writeline($"****dosomethinglong end {name} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")} {lresult}****"); 15 } 16 #endregion
一:task启动任务的几种方式
1 { 2 task task = new task(() => this.dosomethinglong("btntask_click_1")); 3 task.start(); 4 } 5 { 6 task task = task.run(() => this.dosomethinglong("btntask_click_2")); 7 } 8 { 9 taskfactory taskfactory = task.factory;// task.factory等同于: new taskfactory() 10 task task = taskfactory.startnew(() => this.dosomethinglong("btntask_click_3")); 11 }
二:task.delay()和thread.sleep()方法的比较运用
1 private void test() 2 { 3 console.writeline($"****************btntask_click start {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***************"); 4 { 5 stopwatch stopwatch = new stopwatch(); 6 stopwatch.start(); 7 console.writeline("在sleep之前"); 8 thread.sleep(2000); //同步等待--当前线程等待2s 然后继续 9 console.writeline("在sleep之后"); 10 stopwatch.stop(); 11 console.writeline($"sleep耗时{stopwatch.elapsedmilliseconds}"); 12 } 13 { 14 stopwatch stopwatch = new stopwatch(); 15 stopwatch.start(); 16 console.writeline("在delay之前"); 17 task task = task.delay(2000) 18 .continuewith(t => 19 { 20 stopwatch.stop(); 21 console.writeline($"delay耗时{stopwatch.elapsedmilliseconds}"); 22 console.writeline($"this is threadid={thread.currentthread.managedthreadid.tostring("00")}"); 23 });//异步等待--等待2s后启动新任务 24 console.writeline("在delay之后"); 25 } 26 console.writeline($"****************btntask_click end {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***************"); 27 }
通过执行结果如下:
通过观察发现:
- sleep是同步执行方法,即是遇到sleep先等待,然后才能接着做其它的
- delay是异步执行方法,一般不会单独使用,而是会跟continuewith等一起联合使用,即是重新开启一个线程,这个线程多少时间之后执行!
三:taskfactory类的continuewhenany 和 continuewhenall
1 2 /// <summary> 3 /// 模拟coding过程 4 /// </summary> 5 /// <param name="name"></param> 6 /// <param name="projectname"></param> 7 private void coding(string name, string projectname) 8 { 9 console.writeline($"***coding start {name} {projectname} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 10 long lresult = 0; 11 for (int i = 0; i < 1000000000; i++) 12 { 13 lresult += i; 14 } 15 16 console.writeline($"***coding end {name} {projectname} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")} {lresult}***"); 17 } 18 private void test() 19 { 20 console.writeline($"***btntask_click start {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 21 taskfactory taskfactory = new taskfactory(); 22 list<task> tasklist = new list<task>(); 23 tasklist.add(taskfactory.startnew(o => this.coding("aa", "portal"), "aa")); 24 tasklist.add(taskfactory.startnew(o => this.coding("bb", " dba "), "bb")); 25 tasklist.add(taskfactory.startnew(o => this.coding("cc", "client"), "cc")); 26 tasklist.add(taskfactory.startnew(o => this.coding("dd", "backservice"), " dd")); 27 tasklist.add(taskfactory.startnew(o => this.coding("ee", "wechat"), "ee")); 28 29 //谁第一个完成,获取一个红包奖励 30 taskfactory.continuewhenany(tasklist.toarray(), t => console.writeline($"{t.asyncstate}开发完成,获取个红包奖励{thread.currentthread.managedthreadid.tostring("00")}")); 31 tasklist.add(taskfactory.continuewhenall(tasklist.toarray(), rarray => console.writeline($"开发都完成,一起庆祝一下{thread.currentthread.managedthreadid.tostring("00")}"))); 32 //continuewhenany continuewhenall 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程 33 console.writeline($"***btntask_click end {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 34 }
通过结果我们总结如下:
- continuewhenany:等待任意一条完成
- continuewhenall :等待所有的完成
- continuewhenany 和 continuewhenall 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程
四:task中的 waitany 和 waitall
1 /// <summary> 2 /// 模拟coding过程 3 /// </summary> 4 /// <param name="name"></param> 5 /// <param name="projectname"></param> 6 private void coding(string name, string projectname) 7 { 8 console.writeline($"***coding start {name} {projectname} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 9 long lresult = 0; 10 for (int i = 0; i < 1000000000; i++) 11 { 12 lresult += i; 13 } 14 thread.sleep(2000); 15 console.writeline($"***coding end {name} {projectname} {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")} {lresult}***"); 16 } 17 private void test() 18 { 19 console.writeline($"***btntask_click start {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 20 taskfactory taskfactory = new taskfactory(); 21 list<task> tasklist = new list<task>(); 22 tasklist.add(taskfactory.startnew(o => this.coding("aa", "portal"), "aa")); 23 tasklist.add(taskfactory.startnew(o => this.coding("bb", " dba "), "bb")); 24 tasklist.add(taskfactory.startnew(o => this.coding("cc", "client"), "cc")); 25 tasklist.add(taskfactory.startnew(o => this.coding("dd", "backservice"), " dd")); 26 tasklist.add(taskfactory.startnew(o => this.coding("ee", "wechat"), "ee")); 27 28 //阻塞当前线程,等着任意一个任务完成 29 task.waitany(tasklist.toarray());//也可以限时等待,如果是winform界面会卡 30 console.writeline("第一个模块已经完成,现在开始准备环境开始部署"); 31 //需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成,如果是winform界面会卡 32 task.waitall(tasklist.toarray()); 33 console.writeline("5个模块全部完成,准备联测"); 34 console.writeline($"***btntask_click end {thread.currentthread.managedthreadid.tostring("00")} {datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff")}***"); 35 }
运行执行如下:
通过上面得到如下:
- 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中,可以通过如下代码实现:
1 task.run<int>(() => 2 { 3 thread.sleep(2000); 4 return datetime.now.year; 5 }).continuewith(tint => 6 { 7 int i = tint.result; //有堵塞 8 });
注意:使用.result这个会堵塞线程
七:task的线程是源于线程池,线程池是单例的,全局唯一
我们可以通过下面代码来测试一下:
1 threadpool.setmaxthreads(8, 8); 2 for (int i = 0; i < 100; i++) 3 { 4 int k = i; 5 task.run(() => 6 { 7 console.writeline($"this is {k} running threadid={thread.currentthread.managedthreadid.tostring("00")}"); 8 thread.sleep(2000); 9 }); 10 }
设置线程池最大线程为8个后,我们通过监测结果发现:同时并发的task只有8个,而且线程id是重复出现的,即线程是复用的。所以线程池是是全局的,以后要慎重设置线程池数量。
八:parallel是来源于命名空间为:system.threading.tasks,并发执行多个action 多线程。主线程会参与计算---阻塞界面,等于taskwaitall+主线程计算
具体的一些用法如下:
1 { 2 //启动5个任务 3 parallel.invoke(() => this.dosomethinglong("btnparallel_click_1"), 4 () => this.dosomethinglong("btnparallel_click_2"), 5 () => this.dosomethinglong("btnparallel_click_3"), 6 () => this.dosomethinglong("btnparallel_click_4"), 7 () => this.dosomethinglong("btnparallel_click_5")); 8 } 9 { 10 //启动5个任务,i是从0-4 11 parallel.for(0, 5, i => this.dosomethinglong($"btnparallel_click_{i}")); 12 } 13 { 14 //启动5个任务 15 parallel.foreach(new int[] { 0, 1, 2, 3, 4 }, i => this.dosomethinglong($"btnparallel_click_{i}")); 16 }
上面三种方法都是可以的。然后parallel是线程阻塞,那我们可不可以不堵塞线程呢,这个我们可以重启一个任务,让子线程去做这个事情,如下:
1 task.run(() => 2 { 3 paralleloptions options = new paralleloptions(); 4 options.maxdegreeofparallelism = 3; 5 parallel.for(0, 10, options, i => this.dosomethinglong($"btnparallel_click_{i}")); 6 });
这是一种思路,以后会有很多地方用到。
九:控制线程数量
上面的task和taskfactory以及parallel都已经学习完了,我们晓的线程是根据电脑资源有关系的,有时候批量启动n个线程,效率还不如单线程高,因为会有线程切换是要耗时的,那我们怎么控制保证一次调用多个线程呢,下面提供task和parallel的两种解决方案,具体如下:
1 { 2 //parallel设置最大线程为3 3 paralleloptions options = new paralleloptions(); 4 //设置最大线程为3,以后parallel想要控制线程数量只需要设置paralleloptions类中的maxdegreeofparallelism即可 5 options.maxdegreeofparallelism = 3; 6 parallel.for(0, 10, options, i => this.dosomethinglong($"btnparallel_click_{i}")); 7 } 8 { 9 //task设置最大线程为3 10 list<task> tasklist = new list<task>(); 11 for (int i = 0; i < 10000; i++) 12 { 13 int k = i; 14 if (tasklist.count(t => t.status != taskstatus.rantocompletion) >= 3) 15 { 16 task.waitany(tasklist.toarray()); 17 tasklist = tasklist.where(t => t.status != taskstatus.rantocompletion).tolist(); 18 } 19 tasklist.add(task.run(() => 20 { 21 console.writeline($"this is {k} running threadid={thread.currentthread.managedthreadid.tostring("00")}"); 22 thread.sleep(2000); 23 })); 24 } 25 }
学完上面的异步方法,thread和task,有人会提出以下问题:
1:什么时候能用多线程? 任务能并发的时候能够使用多线程
2:多线程能干嘛?多线程能够提升速度/优化用户体验,以cpu资源来换时间