欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# Task

程序员文章站 2022-04-28 21:36:41
在https://www.cnblogs.com/loverwangshan/p/10415937.html中我们有讲到委托的异步方法,Thread,ThreadPool,然后今天来讲一下Task, ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的 ......

在中我们有讲到委托的异步方法,thread,threadpool,然后今天来讲一下task,

threadpool相比thread来说具备了很多优势,但是threadpool却又存在一些使用上的不方便。比如:

  1. threadpool不支持线程的取消、完成、失败通知等交互性操作
  2.  threadpool不支持线程执行的先后次序

以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在.netframwork3.0出现的task,线程是基于线程池,然后提供了丰富的api

下面我们来初步认识一下task,下面我们先新增一个公共的方法以下方法的演示中都会用到:

C# 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
view code

 

一: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()方法的比较运用

C# Task
 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 }
view code

通过执行结果如下:

C# Task

通过观察发现:

  • sleep是同步执行方法,即是遇到sleep先等待,然后才能接着做其它的
  • delay是异步执行方法,一般不会单独使用,而是会跟continuewith等一起联合使用,即是重新开启一个线程,这个线程多少时间之后执行!

三:taskfactory类的continuewhenany 和 continuewhenall

C# Task
 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   }
view code

C# Task

通过结果我们总结如下:

  • continuewhenany:等待任意一条完成
  • continuewhenall :等待所有的完成
  • continuewhenany 和 continuewhenall 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程

 

四:task中的 waitany 和 waitall

C# Task
 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  }
view code

运行执行如下:

C# Task

通过上面得到如下:

  • 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的两种解决方案,具体如下:

C# Task
 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 }
view code

 

学完上面的异步方法,thread和task,有人会提出以下问题:

1:什么时候能用多线程? 任务能并发的时候能够使用多线程
2:多线程能干嘛?多线程能够提升速度/优化用户体验,以cpu资源来换时间