我是伊只雄熊

导航

异步多线程 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)。

posted on 2018-02-06 19:42  我是伊只雄熊  阅读(286)  评论(0编辑  收藏  举报