zoukankan      html  css  js  c++  java
  • C#多线程编程(1)线程,线程池和Task

      新开了一个多线程编程系列,该系列主要讲解C#中的多线程编程。    利用多线程的目的有2个: 一是防止UI线程被耗时的程序占用,导致界面卡顿;二是能够利用多核CPU的资源,提高运行效率。

      我没有进行很深入的讲解,是以实际使用为主。我的这个系列主要是《CLR via C#》的总结,该书的作者Jeffrey Richter是C#的顾问,他本人对windows见解极深。尤其是多线程部分,书中讲解的非常透彻,文中讲解不到或者你想要更深入的了解的同学,可以找来《CLR via C#》仔细研究。

    当一个会执行很长时间的程序,如从服务端获取数据,当该程序执行过程中,客户端一直处于等待状态,等待该程序执行完成,然后再执行其他代码。若是UI程序,用户会感到界面卡顿,影响使用体验。我们希望这样卡顿的程序能够“偷偷”在后台跑,不要影响到界面。解决这个问题就要使用多线程,其中一部分线程负责响应界面操作,另一部分线程负责后台计算。代码如下: 

    public void GetData()
    {
    var thread = new Thread(() => LoadDataFromServer()); thread.start(); }
    public void LoadDataFromServer(){
      //模拟数据读取
      Thread.Sleep(2000);
      Console.WriteLine("读取完成。");
    }

      thread就是你创建的线程,然后调用Start()方法,该线程就会开始执行,LoadDataFromServer()是你想要执行的方法,这里是从服务读取数据,Windows会负责调度这个线程,决定这个线程什么时候开始执行。这样就可以做到新线程负责读取数据,主线程不等待,继续执行,界面不卡顿。这样做很好,因为做到了异步,界面很流畅,但是这不是最优解。当程序执行很长时间,每一次从服务端读取数据,为了不造成界面卡顿,就要新创建个线程。当数据加载完成后,新线程就没用了。创建一个线程开销很大(具体开销就不介绍了,感兴趣的可以上网查相关资料,《Clr via C#》中有很详细的介绍),如果每一次被创建的线程在运行结束后,不被释放,而是存起来,留下一次使用,这样是不是就可以节省资源?线程池就是干这个的,例子如下:

    //一些操作
    ThreadPool.QueueUserWorkItem(()=>LoadDataFromServer());
    //其他操作

    可以看到,上段代码没有显式创建线程,而是把方法放到了ThreadPool.QueueUserWorkItem()方法中,ThreadPool负责创建和管理线程。当程序刚开始时,ThreadPool第一次被调用,这时线程池里一个线程没有,线程池会创建一个新线程,当程序再次调用线程池时,若线程池忠还有空闲线程,则直接调用空闲线程执行程序;若程序调用线程池时,线程池中没有空闲线程且CPU处于“未饱和”状态,则线程池会创建新线程。实际上,当调用线程池时,相当于把要执行的方法“挂”在线程池的任务队列上,当CPU处于“未饱和”状态,线程池就会调用线程来执行线程池任务队列中的任务。

      ThreadPool.QueueUserWorkItem()方法有一个问题,那就是没有很便捷的方法获得方法的返回值,不知道LoadDataFromServer()方法何时执行完成。为了解决这个问题,C#引入了Task,和泛型Task<T>。代码如下

    var data = Task.Run(() => LoadDataFromServer()).Result;

      先讲解一下,Task.Run()是对ThreadPool.QueueUserWorkItem()方法的封装,该方法会返回Task,然后可以通过调用task.Result来获得LoadDataFromServer()的返回值。实际上这段代码并不会异步执行,原因是data所在的线程会等待LoadDataFromServer()的返回值,不然data会没有值,程序无法执行,所以此时线程被阻塞,知道任务完成,该线程才会继续执行。为了解决这一问题,C#引入了async 和 await 两个关键字。代码如下:

    public async void LoadData(){
        var data = await Task.Run(() => LoadDataFromServer());
      Console.WriteLine(data); }
    public string LoadDataFromServer(){
      //模拟到服务器读取数据
      Thread.Sleep(2000);
      return "Data";
    }

      C#规定只能在标有async的方法中使用await 关键字,该关键字会将await后面的代码编译成状态机,在LoadDataFromServer()方法执行结束后,程序会重新进入LoadData()方法,并从await处继续执行,该关键字不会阻塞线程(编译器如何将await的异步方法编译成状态机,《CLR via C#》28.4节有详细讲解)。

      以上就是多线程编程的第一部分--Thread, ThreadPool和Task的讲解,下一节会继续讲解Task的其他特性与方法。

  • 相关阅读:
    了解 NoSQL 的必读资料
    关于什么时候用assert(断言)的思考
    这次见到了一些大侠
    NetBeans 时事通讯(刊号 # 87 Jan 12, 2010)
    动态链接库dll,静态链接库lib, 导入库lib
    新女性十得 写得了代码,查得出异常
    记录系统乱谈
    新女性十得 写得了代码,查得出异常
    fullpage.js禁止滚动
    RunningMapReduceExampleTFIDF hadoopclusternet This document describes how to run the TFIDF MapReduce example against ascii books. This project is for those who wants to experiment hadoop as a skunkworks in a small cluster (110 nodes) Google Pro
  • 原文地址:https://www.cnblogs.com/jazzpop/p/8514619.html
Copyright © 2011-2022 走看看