新开了一个多线程编程系列, 该系列主要讲解 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 的其他特性与方法
来源: http://www.bubuko.com/infodetail-2517767.html