• C# Task 篇幅一


    https://www.cnblogs.com/loverwangshan/p/10415937.html中我们有讲到委托的异步方法,Thread,ThreadPool,然后今天来讲一下Task,

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

    1. ThreadPool不支持线程的取消、完成、失败通知等交互性操作
    2.  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
    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()方法的比较运用

     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

    通过执行结果如下:

    通过观察发现:

    • 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   }
    View Code

    通过结果我们总结如下:

    • 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  }
    View Code

    运行执行如下:

    通过上面得到如下:

    • 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 }
    View Code

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

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

  • 相关阅读:
    拦截器
    git和bootstrap
    java面试题目
    Struts2笔记
    sql语句的面试题
    公司面试总结
    面试题12 包含 min 函数的栈 【栈】
    面试题11 字符串的排列[算法]
    [面试] 进程和线程的区别(面试题)
    [baidu] 面向对象的三个基本要素和五项基本设计原则
  • 原文地址:https://www.cnblogs.com/loverwangshan/p/10444773.html
Copyright © 2020-2023  润新知