zoukankan      html  css  js  c++  java
  • Task底层实现原理探秘

      .NET 4包含新名称空间System.Threading.Tasks,它 包含的类抽象出了线程功能, 在底层使用ThreadPool。 任务表示应完成的某个单元的工作。 这个单元的工作可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主调线程。 使用任务不仅可以获得一个抽象层,还可以对底层线程进行很多控制。   

      在安排需要完成的工作时,任务提供了非常大的灵活性。 例如,可 以定义连续的工 作—— 在一个任务完成后该执行什么工作。 这可以区分任务成功与否。 另外,还可以在层次结构中安排任务。例如,父任务可以创建新的子任务。 这可以创建一种依赖关系,这样,取消父任务,也会取消其子任务。

      asyn和awit,通过获取task句柄的方式,在适当的时候根据句柄回调,这种语法糖更方便的实现异步编程。

    Task和Thread的区别

    • Task是架构在Thread之上的,也就是说任务最终还是要抛给线程去执行。
    • Task跟Thread不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。

    Task控制台测试程序

    1.  
      using System;
    2.  
      using System.Collections.Generic;
    3.  
      using System.Linq;
    4.  
      using System.Text;
    5.  
      using System.Threading;
    6.  
      using System.Threading.Tasks;
    7.  
       
    8.  
      namespace MyTask
    9.  
      {
    10.  
      class Program
    11.  
      {
    12.  
      /// <summary>
    13.  
      /// 启动方式
    14.  
      /// </summary>
    15.  
      public static void StartWay()
    16.  
      {
    17.  
      var task1 = new Task(() =>
    18.  
      {
    19.  
      Console.WriteLine("Hello,task");
    20.  
      });
    21.  
      task1.Start();
    22.  
       
    23.  
      var task2 = Task.Factory.StartNew(() =>
    24.  
      {
    25.  
      Console.WriteLine("Hello,task started by task factory");
    26.  
      });
    27.  
      }
    28.  
       
    29.  
      /// <summary>
    30.  
      /// 生命周期测试
    31.  
      /// </summary>
    32.  
      public static void LifeCircle()
    33.  
      {
    34.  
      var task1 = new Task(() =>
    35.  
      {
    36.  
      Console.WriteLine("Begin");
    37.  
      System.Threading.Thread.Sleep(2000);
    38.  
      Console.WriteLine("Finish");
    39.  
      });
    40.  
      Console.WriteLine("Before start:" + task1.Status);
    41.  
      task1.Start();
    42.  
      Console.WriteLine("After start:" + task1.Status);
    43.  
      task1.Wait();
    44.  
      Console.WriteLine("After Finish:" + task1.Status);
    45.  
       
    46.  
      }
    47.  
       
    48.  
      /// <summary>
    49.  
      /// 等待所有任务完成
    50.  
      /// </summary>
    51.  
      public static void WaitAll()
    52.  
      {
    53.  
      var task1 = new Task(() =>
    54.  
      {
    55.  
      Console.WriteLine("Task 1 Begin");
    56.  
      System.Threading.Thread.Sleep(2000);
    57.  
      Console.WriteLine("Task 1 Finish");
    58.  
      });
    59.  
      var task2 = new Task(() =>
    60.  
      {
    61.  
      Console.WriteLine("Task 2 Begin");
    62.  
      System.Threading.Thread.Sleep(3000);
    63.  
      Console.WriteLine("Task 2 Finish");
    64.  
      });
    65.  
       
    66.  
      task1.Start();
    67.  
      task2.Start();
    68.  
      Task.WaitAll(task1, task2);
    69.  
      Console.WriteLine("WaitAll task finished!");
    70.  
       
    71.  
      }
    72.  
       
    73.  
      /// <summary>
    74.  
      /// 等待任意一个执行任务完成
    75.  
      /// </summary>
    76.  
      public static void WaitAny()
    77.  
      {
    78.  
      var task1 = new Task(() =>
    79.  
      {
    80.  
      Console.WriteLine("Task 1 Begin");
    81.  
      System.Threading.Thread.Sleep(2000);
    82.  
      Console.WriteLine("Task 1 Finish");
    83.  
      });
    84.  
      var task2 = new Task(() =>
    85.  
      {
    86.  
      Console.WriteLine("Task 2 Begin");
    87.  
      System.Threading.Thread.Sleep(3000);
    88.  
      Console.WriteLine("Task 2 Finish");
    89.  
      });
    90.  
       
    91.  
      task1.Start();
    92.  
      task2.Start();
    93.  
      Task.WaitAny(task1, task2);
    94.  
      Console.WriteLine("WaitAny task finished!");
    95.  
      }
    96.  
       
    97.  
      /// <summary>
    98.  
      /// 任务回调方法
    99.  
      /// </summary>
    100.  
      public static void ContinueWith()
    101.  
      {
    102.  
      var task1 = new Task(() =>
    103.  
      {
    104.  
      Console.WriteLine("Task 1 Begin");
    105.  
      System.Threading.Thread.Sleep(2000);
    106.  
      Console.WriteLine("Task 1 Finish");
    107.  
      });
    108.  
      var task2 = new Task(() =>
    109.  
      {
    110.  
      Console.WriteLine("Task 2 Begin");
    111.  
      System.Threading.Thread.Sleep(3000);
    112.  
      Console.WriteLine("Task 2 Finish");
    113.  
      });
    114.  
       
    115.  
       
    116.  
      task1.Start();
    117.  
      task2.Start();
    118.  
      var result = task1.ContinueWith<string>(task =>
    119.  
      {
    120.  
      Console.WriteLine("task1 finished!");
    121.  
      return "This is task1 result!";
    122.  
      });
    123.  
       
    124.  
      Console.WriteLine(result.Result.ToString());
    125.  
       
    126.  
      var result2 = task2.ContinueWith<string>(task =>
    127.  
      {
    128.  
      Console.WriteLine("task2 finished!");
    129.  
      return "This is task2 result!";
    130.  
      });
    131.  
       
    132.  
      Console.WriteLine(result2.Result.ToString());
    133.  
      }
    134.  
       
    135.  
      /// <summary>
    136.  
      /// 取消任务
    137.  
      /// </summary>
    138.  
      public static void Cancel()
    139.  
      {
    140.  
      var tokenSource = new CancellationTokenSource();
    141.  
      var token = tokenSource.Token;
    142.  
      var task = Task.Factory.StartNew(() =>
    143.  
      {
    144.  
      for (var i = 0; i < 1000; i++)
    145.  
      {
    146.  
      System.Threading.Thread.Sleep(1000);
    147.  
      if (token.IsCancellationRequested)
    148.  
      {
    149.  
      Console.WriteLine("Abort mission success!");
    150.  
      return;
    151.  
      }
    152.  
      }
    153.  
      }, token);
    154.  
      token.Register(() =>
    155.  
      {
    156.  
      Console.WriteLine("Canceled");
    157.  
      });
    158.  
      Console.WriteLine("Press enter to cancel task...");
    159.  
      Console.ReadKey();
    160.  
      tokenSource.Cancel();
    161.  
       
    162.  
      Console.ReadKey();//这句忘了加,程序退出了,看不到“Abort mission success!“这个提示
    163.  
      }
    164.  
       
    165.  
      /// <summary>
    166.  
      /// 函数入口
    167.  
      /// </summary>
    168.  
      /// <param name="args"></param>
    169.  
      static void Main(string[] args)
    170.  
      {
    171.  
      Console.WriteLine("StartWay is runing");
    172.  
      StartWay();
    173.  
      Console.WriteLine("LifeCircle is runing");
    174.  
      LifeCircle();
    175.  
      Console.WriteLine("WaitAll is runing");
    176.  
      WaitAll();
    177.  
      Console.WriteLine("WaitAny is runing");
    178.  
      WaitAny();
    179.  
      Console.WriteLine("ContinueWith is runing");
    180.  
      ContinueWith();
    181.  
      Console.WriteLine("Cancel is runing");
    182.  
      Cancel();
    183.  
       
    184.  
      Console.Read();
    185.  
      }
    186.  
      }
    187.  
      }

    参考地址:http://www.cnblogs.com/yunfeifei/p/4106318.html

    内嵌和异常

    1.  
      using System;
    2.  
      using System.Collections.Generic;
    3.  
      using System.Linq;
    4.  
      using System.Text;
    5.  
      using System.Threading;
    6.  
      using System.Threading.Tasks;
    7.  
       
    8.  
      namespace MyTask
    9.  
      {
    10.  
      class Program
    11.  
      {
    12.  
       
    13.  
      /// <summary>
    14.  
      /// 内部Task测试-普通方式
    15.  
      /// <mark>
    16.  
      /// 执行结果:
    17.  
      /// Parent task finished!
    18.  
      /// Flag
    19.  
      /// Childen task finished!
    20.  
      /// </mark>
    21.  
      /// </summary>
    22.  
      public static void InnerTask()
    23.  
      {
    24.  
      var pTask = Task.Factory.StartNew(() =>
    25.  
      {
    26.  
      var cTask = Task.Factory.StartNew(() =>
    27.  
      {
    28.  
      System.Threading.Thread.Sleep(2000);
    29.  
      Console.WriteLine("Childen task finished!");
    30.  
      });
    31.  
      Console.WriteLine("Parent task finished!");
    32.  
      });
    33.  
      pTask.Wait();
    34.  
      Console.WriteLine("Flag");
    35.  
      }
    36.  
       
    37.  
      /// <summary>
    38.  
      /// 内部Task测试-父子任务关联
    39.  
      /// <mark>
    40.  
      /// 执行结果:
    41.  
      /// Parent task finished!
    42.  
      /// Childen task finished!
    43.  
      /// Flag
    44.  
      /// </mark>
    45.  
      /// </summary>
    46.  
      public static void InnerTaskAttachedToParent()
    47.  
      {
    48.  
      var pTask = Task.Factory.StartNew(() =>
    49.  
      {
    50.  
      var cTask = Task.Factory.StartNew(() =>
    51.  
      {
    52.  
      System.Threading.Thread.Sleep(2000);
    53.  
      Console.WriteLine("Childen task finished!");
    54.  
      }, TaskCreationOptions.AttachedToParent);
    55.  
      Console.WriteLine("Parent task finished!");
    56.  
      });
    57.  
      pTask.Wait();
    58.  
      Console.WriteLine("Flag");
    59.  
      }
    60.  
       
    61.  
      /// <summary>
    62.  
      /// 多任务互相关联结果运算
    63.  
      /// </summary>
    64.  
      public static void MultiInnerTask()
    65.  
      {
    66.  
      Task.Factory.StartNew(() =>
    67.  
      {
    68.  
      var t1 = Task.Factory.StartNew<int>(() =>
    69.  
      {
    70.  
      Console.WriteLine("Task 1 running...");
    71.  
      return 1;
    72.  
      });
    73.  
      t1.Wait(); //等待任务一完成
    74.  
      var t3 = Task.Factory.StartNew<int>(() =>
    75.  
      {
    76.  
      Console.WriteLine("Task 3 running...");
    77.  
      return t1.Result + 3;
    78.  
      });
    79.  
      var t4 = Task.Factory.StartNew<int>(() =>
    80.  
      {
    81.  
      Console.WriteLine("Task 2 running...");
    82.  
      return t1.Result + 2;
    83.  
      }).ContinueWith<int>(task =>
    84.  
      {
    85.  
      Console.WriteLine("Task 4 running...");
    86.  
      return task.Result + 4;
    87.  
      });
    88.  
      Task.WaitAll(t3, t4); //等待任务三和任务四完成
    89.  
      var result = Task.Factory.StartNew(() =>
    90.  
      {
    91.  
      Console.WriteLine("Task Finished! The result is {0}", t3.Result + t4.Result);
    92.  
      });
    93.  
      });
    94.  
      }
    95.  
       
    96.  
      /// <summary>
    97.  
      /// 函数入口
    98.  
      /// </summary>
    99.  
      /// <param name="args"></param>
    100.  
      static void Main(string[] args)
    101.  
      {
    102.  
      Console.WriteLine("InnerTask is runing");
    103.  
      InnerTask();
    104.  
      Console.WriteLine("InnerTaskAttachedToParent is runing");
    105.  
      InnerTaskAttachedToParent();
    106.  
      Console.WriteLine("MultiInnerTask is runing");
    107.  
      MultiInnerTask();
    108.  
       
    109.  
      Console.Read();
    110.  
      }
    111.  
      }
    112.  
      }

    参考地址:http://www.cnblogs.com/yunfeifei/p/4111112.html

    多线程带来的问题

    1、死锁问题 

      前面我们学习了Task的使用方法,其中Task的等待机制让我们瞬间爱上了它,但是如果我们在调用Task.WaitAll方法等待所有线程时,如果有一个Task一直不返回,会出现什么情况呢?当然,如果我们不退出来的话,程序会一直等待下去,那么因为这一个Task的死锁,导致其他的任务也无法正常提交,整个程序"死"在那里。下面我们来写一段代码,来看一下死锁的情况:

             var t1 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task 1 Start running...");
                    while(true)
                    {
                        System.Threading.Thread.Sleep(1000);
                    }
                    Console.WriteLine("Task 1 Finished!");
                });
                var t2 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task 2 Start running...");
                    System.Threading.Thread.Sleep(2000);
                    Console.WriteLine("Task 2 Finished!");
                });
                Task.WaitAll(t1,t2);

    这里我们创建两个Task,t1和t2,t1里面有个while循环,由于条件一直为TRUE,所以他永远也无法退出。运行程序,结果如下:

    可以看到Task2完成了,就是迟迟等不到Task1,这个时候我们按回车是没有反应的,除非关掉窗口。如果我们在项目中遇到这种情况是令人很纠结的,因为我们也不知道到底发生了什么,程序就是停在那里,也不报错,也不继续执行。

    那么出现这种情况我们该怎么处理呢?我们可以设置最大等待时间,如果超过了等待时间,就不再等待,下面我们来修改代码,设置最大等待时间为5秒(项目中可以根据实际情况设置),如果超过5秒就输出哪个任务出错了,代码如下:

               Task[] tasks = new Task[2];
                tasks[0] = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task 1 Start running...");
                    while(true)
                    {
                        System.Threading.Thread.Sleep(1000);
                    }
                    Console.WriteLine("Task 1 Finished!");
                });
               tasks[1] = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task 2 Start running...");
                    System.Threading.Thread.Sleep(2000);
                    Console.WriteLine("Task 2 Finished!");
                });
                
                Task.WaitAll(tasks,5000);
                for (int i = 0; i < tasks.Length;i++ )
                {
                    if (tasks[i].Status != TaskStatus.RanToCompletion)
                    {
                        Console.WriteLine("Task {0} Error!",i + 1);
                    }
                }
                Console.Read();

    这里我们将所有任务放到一个数组里面进行管理,调用Task.WaitAll的一个重载方法,第一个参数是Task[]数据,第二个参数是最大等待时间,单位是毫秒,这里我们设置为5000及等待5秒钟,就继续向下执行。下面我们遍历Task数组,通过Status属性判断哪些Task没有完成,然后输出错误信息。

    2、SpinLock(自旋锁)

       我们初识多线程或者多任务时,第一个想到的同步方法就是使用lock或者Monitor,然而在4.0 之后微软给我们提供了另一把利器——spinLock,它比重量级别的Monitor具有更小的性能开销,它的用法跟Monitor很相似,VS给的提示如下:

    下面我们来写一个例子看一下,代码如下(关于lock和Monitor的用法就不再细说了,网上资料很多,大家可以看看):

              SpinLock slock = new SpinLock(false);
                long sum1 = 0;
                long sum2 = 0;
                Parallel.For(0, 100000, i =>
                {
                    sum1 += i;
                });
    
                Parallel.For(0, 100000, i =>
                {
                    bool lockTaken = false;
                    try
                    {
                        slock.Enter(ref lockTaken);
                        sum2 += i;
                    }
                    finally
                    {
                        if (lockTaken)
                            slock.Exit(false);
                    }
                });
    
                Console.WriteLine("Num1的值为:{0}", sum1);
                Console.WriteLine("Num2的值为:{0}", sum2);
    
                Console.Read();

    输出结果如图:

    这里我们使用了Parallel.For方法来做演示,Parallel.For用起来方便,但是在实际开发中还是尽量少用,因为它的不可控性太高,有点简单粗暴的感觉,可能带来一些不必要的"麻烦",最好还是使用Task,因为Task的可控性较好。

    slock.Enter方法,解释如下:

    3、多线程之间的数据同步

      多线程间的同步,在用thread的时候,我们常用的有lock和Monitor,上面刚刚介绍了.Net4.0中一个新的锁——SpinLock(自旋锁),实际上,我们还可以将任务分成多块,由多个线程一起执行,最后合并多个线程的结果,如:求1到100的和,我们分10个线程,分别求1~10,......,90~100的和,然后合并十个线程的结果。还有就是使用线程安全集合,可参加第二天的文章。其实Task的同步机制做已经很好了,如果有特殊业务需求,有线程同步问题,大家可一起交流~~

     Task和线程池之间的抉择

      我们要说的task的知识也说的差不多了,接下来我们开始站在理论上了解下“线程池”和“任务”之间的关系,我们要做到知其然,还要知其所以然。不管是说线程还是任务,我们都不可避免的要讨论下线程池,然而在.net 4.0以后,线程池引擎考虑了未来的扩展性,已经充分利用多核微处理器架构,只要在可能的情况下,我们应该尽量使用task,而不是线程池。

       这里简要的分析下CLR线程池,其实线程池中有一个叫做“全局队列”的概念,每一次我们使用QueueUserWorkItem的使用都会产生一个“工作项”,然后“工作项”进入“全局队列”进行排队,最后线程池中的的工作线程以FIFO(First Input First Output)的形式取出,这里值得一提的是在.net 4.0之后“全局队列”采用了无锁算法,相比以前版本锁定“全局队列”带来的性能瓶颈有了很大的改观。那么任务委托的线程池不光有“全局队列”,而且每一个工作线程都有”局部队列“。我们的第一反应肯定就是“局部队列“有什么好处呢?这里暂且不说,我们先来看一下线程池中的任务分配,如下图:

    线程池的工作方式大致如下,线程池的最小线程数是6,线程1~3正在执行任务1~3,当有新的任务时,就会向线程池请求新的线程,线程池会将空闲线程分配出去,当线程不足时,线程池就会创建新的线程来执行任务,直到线程池达到最大线程数(线程池满)。总的来说,只有有任务就会分配一个线程去执行,当FIFO十分频繁时,会造成很大的线程管理开销。

      下面我们来看一下task中是怎么做的,当我们new一个task的时候“工作项”就会进去”全局队列”,如果我们的task执行的非常快,那么“全局队列“就会FIFO的非常频繁,那么有什么办法缓解呢?当我们的task在嵌套的场景下,“局部队列”就要产生效果了,比如我们一个task里面有3个task,那么这3个task就会存在于“局部队列”中,如下图的任务一,里面有三个任务要执行,也就是产生了所谓的"局部队列",当任务三的线程执行完成时,就会从任务一种的队列中以FIFO的形式"窃取"任务执行,从而减少了线程管理的开销。这就相当于,有两个人,一个人干完了分配给自己的所有活,而另一个人却还有很多的活,闲的人应该接手点忙的人的活,一起快速完成。

      从上面种种情况我们看到,这些分流和负载都是普通ThreadPool.QueueUserWorkItem所不能办到的,所以说在.net 4.0之后,我们尽可能的使用TPL,抛弃ThreadPool。

    原文地址:http://www.cnblogs.com/yunfeifei/p/4122084.html

  • 相关阅读:
    Keil MDK5 硬件仿真之查看变量的值(内附变量值不随程序的运行而刷新的解决办法)
    15 Q点稳定的放大电路和基本共集放大电路
    13 H参数等效模型
    12 放大电路的分析方法
    文件处理
    基本数据类型及内置方法
    for循环及字符串操作
    while循环
    用户交互、运算符
    变量与基本数据类型
  • 原文地址:https://www.cnblogs.com/wangyu19900123/p/11647638.html
Copyright © 2011-2022 走看看