赞
踩
.Net中的多线程概念和异步处理
单个应用程序可能需要执行多个类似的任务
例如: 一个Web服务器可能同时接收很多并发的网页, 图像,声音等客户端请求, 如果是传统的单个进程执行一次只能处理一个请求, 终端等待时间过长
以前是让服务器以单个进程运行接收请求, 当服务器接收到请求创建另一个进程处理, 当时进程的创建很耗费时间和资源, 如果每个请求的新进程都执行同样的任务不需要消耗这么多进程创建的开销
多线程流行之后, 一个进程包含多个线程的操作更加有效, 如果Web服务器是多线程的, 服务器可以创建一个线程用以监听请求, 当请求进来之后创建新的线程处理, 并恢复监听
线程是CPU的一个基本单元, 包含线程ID, 程序计数器, 寄存器组和堆栈
线程可以进入休眠, 等待操作系统唤醒继续执行, 切换线程有一定的性能成本
很多时候程序需要调用一些阻塞操作, 比如IO, 网络连接等, 需要让线程先等待, 操作完成之后重新把线程放入等待运行的队列等待唤醒执行
但是这样一旦阻塞操作很多, 意味着需要开启很多个线程, 开启线程的消耗会造成性能问题
为了解决这种问题, 最初是使用事件循环机制, 例如跨平台的select接口. 程序需要使用一个或多个线程用于获取事件, 然后替换 执行阻塞操作 为 非阻塞操作, 注册事件用于在处理完成后接收通知.
这样发出阻塞操作的线程可以减少为1个, 这一个线程可以同时创建多个阻塞操作. 然后在循环中不断处理事件, 针对不同事件, 及类似事件回调的数据, 执行对应的回调完成操作
基于事件循环编写程序有一定难度, 且代码结构类似, 因此在事件循环基础上封装了一套框架, 称为异步操作, 异步操作提供了基于回调的机制, 会先执行非阻塞操作, 注册事件并关联回调, 接收到事件后自动调用之前关联的回调. 著名的异步操作框架: C语言libevent,C++的ASIO,JAVA的Netty。部分操作系统也提供了原生接口,Windows的IOCP,Linux的AIO。
基于任务的异步模式 (TAP) ,该模式使用单一方法表示异步操作的开始和完成。 TAP 是在 .NET Framework 4 中引入的。 这是在 .NET 中进行异步编程的推荐方法。 C# 中的 async 和 await 关键词以及 Visual Basic 中的 Async 和 Await 运算符为 TAP 添加了语言支持。
基于事件的异步模式 (EAP),是提供异步行为的基于事件的旧模型。 这种模式需要后缀为 Async 的方法,以及一个或多个事件、事件处理程序委托类型和 EventArg 派生类型。 EAP 是在 .NET Framework 2.0 中引入的。 建议新开发中不再使用这种模式。
异步编程模型 (APM) 模式(也称为 IAsyncResult 模式),这是使用 IAsyncResult 接口提供异步行为的旧模型。 在这种模式下,同步操作需要 Begin 和 End 方法(例如,BeginWrite 和 EndWrite以实现异步写入操作)。 不建议新的开发使用此模式。
本文只探讨目前微软推荐的异步模式TAP, TAP基于System.Threading.Task 命名空间的Task和Task来表示异步操作, 单个方法本身可以表示异步操作的开始和结束
Task类提供了异步操作的生命周期, 该周期由TaskStatus枚举表示, Task也包含Exception内容
TAP有以下三种方式实现:
//使用async关键字的方法会被归类为异步方法 public async Task<TResult> GetAsync(int id) { ... return TResult; } //可以手动实现 public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state) { var tcs = new TaskCompletionSource<int>(); stream.BeginRead(buffer, offset, count, ar => { try { tcs.SetResult(stream.EndRead(ar)); } catch (Exception exc) { tcs.SetException(exc); } }, state); return tcs.Task; } //混合方法 public Task<int> MethodAsync(string input) { if (input == null) throw new ArgumentNullException("input"); return MethodAsyncInternal(input); } private async Task<int> MethodAsyncInternal(string input) { // code that uses await goes here return value; }
CLR线程池引擎维护了一定数量的空闲工作线程以支持工作项的执行, 并且能够重用已用线程以避免创建新的不必要线程的花费.
使用爬山算法(hill-climbing algorithm), 依据工作项所需资源的可用情况, 例如:CPU, 网络带宽, 来检查吞吐量, 判断是否需要更多的线程来完成更多的工作项
TaskScheduler是基于CLR线程池引擎实现的, 当任务调度器开始分派任务时:
当使用async标记一个方法时, 即告诉了编译器两件事:
await关键字告诉编译器在async标记的方法中插入一个挂起点(唤醒点)
逻辑上当 await someobject, 编译器将生成代码来检查someobject代表的操作是否完成, 如果已完成, 则从await标记的唤醒点(挂起点)继续同步执行, 如果没有, 将为等待的someobject生成一个continue委托, 当someobject代表的操作完成后回调continue委托, 这个continue委托将控制权移交到async方法对应的await唤醒点
返回到await唤醒点之后, 任何结果都可以从返回的task中提取, 如果异常则异常随着task一起返回给SynchronizationContext(同步上下文)
在await someobject之后, 编译器会生成一个包含MoveNext方法的状态机类, 在实例 someobject上使用这些成员来检查该对象是否已完成(通过 IsCompleted),如果未完成,则挂接一个续体(通过 OnCompleted),当所等待实例最终完成时,系统将再次调用 MoveNext 方法,完成后,来自该操作的任何异常将得到传播或作为结果返回(通过 GetResult),并跳转至上次执行中断的位置。:
private class FooAsyncStateMachine : IAsyncStateMachine { // Member fields for preserving “locals” and other necessary state int $state; TaskAwaiter $awaiter; … public void MoveNext() { // Jump table to get back to the right statement upon resumption switch (this.$state) { … case 2: goto Label2; … } … // Expansion of “await someObject;” this.$awaiter = someObject.GetAwaiter(); if (!this.$awaiter.IsCompleted) { this.$state = 2; this.$awaiter.OnCompleted(MoveNext); return; Label2: } this.$awaiter.GetResult(); … } }
线程本地变量不适用于异步操作代码, 因为异步操作中执行异步的线程和回调的线程不一定相同
.Net实现了异步本地变量, 通过执行上下文(区分于线程上下文)实现, 每个托管线程对象都会保存一个执行上下文对象(用于保存异步本地变量)
任务并行库创建Task时会记录当前托管线程的上下文, 并在执行回调之前恢复
.Net异步编程整理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。