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的其他特性与方法。
C#多线程编程(1)--线程,线程池和Task的更多相关文章
- 多线程编程-- part 2 线程的生命周期和优先级
线程的创建到消亡的历程: java多线程的5种状态: (1)New(新建) new Thread(run()) 该线程还没开始运行,状态是new,在程序运行前还有一些基础工作要做 (2)runnabl ...
- iOS多线程编程之创建线程安全(转载)
一.多线程的安全隐患 资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块资源时,很容易引发数据错乱和数 ...
- 七. 多线程编程2.Java线程模型
Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程.实际上,Java使用线程来使整个环境异步.这有利于通过防止CPU循环的浪费来减少无效部分. 为更好的理解多线程环境的优势可以将它与它 ...
- java多线程编程(二创建线程)
1.概念 因为java是完全面向对象的,所以在java中,我们说的线程,就是Thread类的一个实例对象.所以,一个线程就是一个对象,它有自己字段和方法. 2.创建线程 创建线程有 ...
- UNIX环境编程学习笔记(28)——多线程编程(三):线程的取消
lienhua342014-11-24 1 取消线程 pthread 提供了pthread_cancel 函数用于请求取消同一进程中的其他线程. #include <pthread.h> ...
- iOS多线程编程之创建线程(转载)
一.创建和启动线程简单说明 一个NSThread对象就代表一条线程 (1)创建.启动线程 NSThread *thread = [[NSThread alloc] initWithTarget:sel ...
- 多线程编程-- part 4 线程间的通信
线程间的相互作用 线程之间需要一些协调通信,来共同完成一件任务. Object类相关的方法:notify(),notifyAll(),wait().会被所有的类继承,这些方法是final不能被重写.他 ...
- Java多线程编程之守护线程
Java的线程分为两种,一个是用户线程,一个是守护线程.守护线程守护的对象就是用户线程,当用户线程结束后,守护它的守护线程也就会结束.二者的本质基本是一样的,唯一区别在于何时结束. 用户线程:直到自己 ...
- java核心-多线程(6)-线程池-ThreadPoolExecutor
1.java多线程编程少不了使用线程池,线程池相关的工具类所在jdk包,java.util.concurrent 2.使用示例 demo1 public class ThreadPoolDemo { ...
- Java多线程编程(1)--Java中的线程
一.程序.进程和线程 程序是一组指令的有序集合,也可以将其通俗地理解为若干行代码.它本身没有任何运行的含义,它只是一个静态的实体,它可能只是一个单纯的文本文件,也有可能是经过编译之后生成的可执行文 ...
随机推荐
- 使用DateTimeOffset 对xml中的日期时间格式时区进行处理
在日常使用中难免会与XML打交道,其中一个常用的格式就是日期了. 交互的时候通常有下面2种方式 DECLARE @Doc XML=' <R> <T>2018-02-22+08: ...
- Leetcode刷题C#版之 Length of Last Word
题目: Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return t ...
- SmokePing 部署实践
1 通过 yum 安装依赖的库以及环境 yum install rrdtool wqy* fping curl bind-utils httpd httpd-devel \ perl perl-FCG ...
- Apollo阿波罗配置中心docker
前言 在分布式系统中,要改个配置涉及到很多个系统,一个一个改效率低下,吃力不讨好.用配置中心可以解决这个问题.当然配置中心有不少,以下对比的表格是照搬Apollo Wiki的. 功能点 Apollo ...
- 关于 JS 拖拽功能的冲突问题及解决方法
前言 我在之前写过关于 JS 拖拽的文章,实现方式和网上能搜到的方法大致相同,别无二致,但是在一次偶然的测试中发现,这种绑定事件的方式可能会和其它的拖拽事件产生冲突,由此产生了对于事件绑定的思考.本文 ...
- C/C++语言简介之运算符
比较特别的是,比特右移(>>)运算符可以是算术(左端补最高有效位)或是逻辑(左端补 0)位移.例如,将 11100011 右移 3 比特,算术右移后成为 11111100,逻辑右移则为 0 ...
- acm水题3个:1.求最大公约数;2.水仙花数;3.判断完数
//7.求两个整数的最大公约数#include<stdio.h>//用穷举法求出最大公约数int gcd1(int m,int n){ int min = m > n ? n : m ...
- js鼠标滚轮事件兼容
JavaScript鼠标滚轮事件 IE6.0首先实现了鼠标的滚轮事件,其良好的交互效果得到认可,随后Opera.chrome.safari等主流浏览器都实现了该效果,不过存在着很大的兼容问题. 大多数 ...
- mysql 学习心得5
常用函数 字符串函数 concat(S1,S2....,Sn) 链接s1 s2 ...... 任何字符串和null链接显示为null insert(str,x,y,instr) 将str从x位开始y ...
- scrapy 中日志的使用
我在后台调试 在后台调试scrapy spider的时候,总是觉得后台命令窗口 打印的东西太多了不便于观察日志,因此需要一个日志文件记录信息,这样以后会 方便查找问题. 分两种方法吧. 1.简单粗暴. ...