zoukankan      html  css  js  c++  java
  • 异步多线程 Thread ThreadPool Task

    一、线程 Thread ThreadPool

      线程是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数;多线程的意义在于一个应用程序中,有多个执行部分可以同时执行,一个进程中可以同时创建多个线程同时执行;对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行;这样主线程就不会阻塞,可以继续往下执行;等到后台线程执行完毕,再通知主线程,然后做出对应操作。

    1、线程分前台线程和后台线程

      前台线程应用程序必须运行完所有的前台线程才可以退出

      应用程序的主线程以及使用Thread构造的线程都默认为前台线程;主线程执行完毕后,会等待所有子线程(前台)执行完毕后,才退出程序;前台线程可以修改为后台线程,方式是设置Thread.IsBackground 属性。

      后台线程应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束

      主线程执行完毕后,直接退出程序,不论子线程(后台线程)是否执行完毕,线程池线程都是后台线程,Task工厂创建的线程也是后台线程;后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止;所有剩余的后台线程都会停止且不会完成。

    2、前后台线程使用场景

      后台线程非常适合于完成后台任务,应该将被动侦听活动的线程设置为后台线程,而将负责发送数据的线程设置为前台线程,这样,在所有的数据发送完毕之前该线程不会被终止。一般前台线程用于需要长时间等待的任务,比如监听客户端的请求;后台线程一般用于处理时间较短的任务,比如处理客户端发过来的请求信息。

    3、线程调度 优先级    

      在windows上执行的线程在执行了一定时间(一个时间片,一般30ms)后,windows将会进行“调度”,让其他线程占用CPU执行,而线程优先级来确定调度顺序。也就是说如果存在一个优先级是25的线程能够执行,那么windows将不会调用优先级为24的线程。而windows是一种“抢占式”的操作系统(操作系统将定期的中断当前正在执行的线程,将CPU分配给在等待队列的下一个线程),如果一个具有较高优先级的线程准备好运行,并且当前运行的是较低优先级的线程,windows将迫使较低优先级线程停止运行,开始运行较高优先级的线程。

      由于windows上线程调用是通过线程的优先级来实现的,如果想让一个应用程序能够被尽量多的调度,就需要设置线程的优先级, 可以通过Thread的Priority属性设置,以影响线程的基本优先级。Priority属性需要一个ThreadPriority枚举定义的值。     

           Highest > AboveNormal >  Normal >  BelowNormal > Lowest

      线程池线程的优先级默认为Normal,Thread创建的线程也是Normal

    4、Thread

      创建和销毁线程(Thread)是一个要耗费大量时间的过程,另外,太多的线程也会浪费内存资源,所以通过Thread类来创建过多的线程反而有损于性能,为了改善这样的问题 引入了线程池。

    5、ThreadPool

      线程池形象的表示就是存放应用程序中使用的线程的一个集合。CLR初始化时,线程池中是没有线程的,在内部, 线程池维护了一个操作请求队列,当应用程序想执行一个异步操作时,就调用一个方法,就将一个任务放到线程池的队列中,线程池中代码从队列中提取任务,将这个任务委派给一个线程池线程去执行,当线程池线程完成任务时,线程不会被销毁,而是返回到线程池中,等待响应另一个请求。由于线程不被销毁, 这样就可以避免因为创建线程所产生的性能损失。

      ThreadPool线程分Worker线程和IO线程

      Worker线程:用来完成一些计算的任务,需要CPU不间断地处理,CPU和线程的资源是充分利用的。QueueUserWorkItem方法一般会使用工作者线程。

      IO线程:主要用来完成输入和输出的工作。输入输出操作分成三个步骤:启动、实际输入输出、处理结果。用于实际输入输出可由硬件(IO设备)完成,并不需要CPU的参与,正在运行的线程将处于等待状态,只有等任务完成后才会有事可做, 这样就造成线程资源浪费的问题,异步编程,就是在此处进行动作,让线程不等待,直接回到线程池,而启动和处理结果使用IO线程,而且它们可以在不同一个IO线程上。

      不适合线程池的场景

      (1)、线程池内的所有线程都是默认Normal优先级,要调整线程的优先级,不要使用线程池线程。

      (2)、如果任务执行的时间比较长的话,建议还是自己开线程,因为有可能阻塞了线程池里面的线程最终导致线程池的线程被耗光。

      (3)、如果任务是要马上执行的,建议还是不要使用线程池,因为往线程池提交的任务都需要排队。

      注意:手动创建的Thread和线程池里的线程没有任何关系。

    6、Thread ThreadPool 使用推荐

      推荐使用线程池线程而非新建线程(Thread)。因为就算只是单纯的新建一个线程,这个线程什么事情也不做,都大约需要1M的内存空间来存储执行上下文数据结构,并且线程的创建与回收也需要消耗资源,耗费时间。而线程池的优势在于线程池中的线程是根据需要创建与销毁,是最优的存在。

      然而,线程池线程是后台线程,主线程执行完毕后,不会等待后台线程而直接结束程序。.NET Framework4.0提供的Task,可以解决此类问题。

    二、Task

      ThreadPool优于Thread,但是线程池也有以下缺点:

      (1)、没有一个内建的机制让你知道操作在什么时候完成,也没有一个机制在操作完成是获得一个返回值

      (2)、不支持线程的取消、完成、失败通知等交互性操作

      (3)、不支持线程执行的先后次序,如上边,主线程不会等待后台线程池线程执行结束而直接结束。

      .NET Framework4.0提供的Task在线程池的基础上进行了优化,并提供了更多的API,能解决上述缺点,编写多线程程序,Task已经优于传统的Thread ThreadPool方式。

      Task是更上层的封装,底部还是通过Thread或者ThreadPool实现的。

    1、Task性能更优于ThreadPool

      1)ThreadPool的代码将以先进先出的算法存储在全局队列中,并且多个工作者线程之间竞争同一个同步锁。(这就Task性能优于ThreadPool的第一个原因)

      2)Task的代码将以先进后出的算法存储在本地队列中,工作者线程执行本地队列中的代码没有同步锁的限制(这是Task性能优于ThreadPool的第二个原因),并且当工作者线程2空闲并且工作者线程1忙碌时,工作者线程2会尝试从工作者线程1(或者别的忙碌的工作者线程)的本地队列尾部“偷”任务,并会获取一个同步锁,不过这种行为很少发生。

      

    2、Task.Wait

      同于Thread.Join()方法,主线程上等待另一线程执行完成,可以设置时间,在这个时间没到或另外一个线程没执行完之前,当前线程会等待。另外,Task.Result会调用Wait方法。调用Wait或者Result可以确保代码捕获到异常,并从异常中恢复。

    3、Task.FromResult

      创建一个带返回值的、已完成的Task(获取该Task后,直接Result,否则,Task都要run后才能获取结果)

      场景1:以同步的方式实现一个异步接口方法

       一个接口包含异步方法

    interface IMyInterface
    {
        Task<int> DoSthAsync();
    }

      以同步的方式实现该接口方法DoSthAsync,但要返回异步的结果,没有使用async/await

      实现类MyClass的DoSthAsync方法中,都是以同步方式实现,但返回结果要是Task<int>,使用Task.FromResult刚好能返回一个带值的异步结果。

    public class MyClass : IMyInterface
    {
        public Task<int> DoSthAsync()
        {
            int result = 1;
            return Task.FromResult(result);
        }
    }

      场景2:从缓存中获取值,以同步或者异步的方式实现

      需要根据key从缓存中获取值,如果每个key对应的缓存存在,就直接中缓存中获取值,即同步方式获取一部结果(FromResult),如果不存在,就需要以异步的方式获取缓存。

      异步获取缓存的方法(async /await):

      

    private async Task<string> GetValueAsync(int key)
    {
      //可能是访问数据库(IO)
    string result = await SomeAsyncMethod(); cache.TrySetValye(key, result); return result; }

      以下方法来获取缓存中的值,有可能是异步方式,也有可能是同步的方式(从本地缓存中获取,FromResult)

    //返回的都是异步结果Task<>
    public
    Task<string> GetValueFromCache(int key) { string result = string.Empty; //本地缓存存在,就以同步方式获取一部结果(FromResult方法) if(cache.TryGetValue(key, out result)) { return Task.FromResult(result); }
       //缓存中不存在,就通过异步方法获取
    return GetValueAsync(key); }

      注意,这个方法没有使用async/await,返回值是Task,而且直接调用一部方法GetValueAsync,其里边使用了async/await,相当于是同步调用了一部。

      另外,如果使用Task.FromResult不带返回值,就使用Task.FromResult(0) 或 Task.FromResult(null)。

  • 相关阅读:
    [转]SVN服务器搭建和使用(二)
    [转]SVN服务器搭建和使用(一)
    BZOJ 2049 Sdoi2008 Cave 洞穴勘测
    BZOJ 1589 Usaco2008 Dec Trick or Treat on the Farm 采集糖果
    BZOJ 2796 POI2012 Fibonacci Representation
    BZOJ 2115 Wc2011 Xor
    BZOJ 3105 CQOI2013 新Nim游戏
    BZOJ 2460 Beijing2011 元素
    BZOJ 3687 简单题
    BZOJ 1068 SCOI2008 压缩
  • 原文地址:https://www.cnblogs.com/shawnhu/p/8422817.html
Copyright © 2011-2022 走看看