.Net-C#异步程序知识点梳理
-
线程:
每个Windows进程都有用于进入程序的进入点(entry point)的主线程(Main Thread),例如.Net framework执行程序(控制台、Windows WPF等应用程序)使用Main()方法作为程序的进入点,调用该方法时会自动建立主线程。
线程是Windows进程中的独立执行单元,每个线程都有一个主线程(在执行进入点时建立)并且每个线程还可以以程序方式建立新的线程。
线程的分配与启动需要一定的时间与成本开销,所以这个过程并不是实时的。 -
Task类:
与Thread相比,Task是更抽象的概念,MSDN说明就简单一句:“表示异步操作”,即为能以多线程执行的异步操作,从net framework4.5开始,要建立实现Task后台线程最简单的方式就是使用静态方法Task.Run(),在.Net Framework 4.0可以调用Task.Factory.StartNew方法,可以实现相同的效果
持续工作会告诉Task在完成后继续执行后续的作业,持续工作通常由一个回调方法来实现,它会在任务完成后执行一次。将一个Task加上持续工作有两种方法,
- 第一种是调用Task.ContinueWith方法
- 第二种是.Net Framework4.5新增的Awaiter方法,它非常重要,因为C#5.0的异步功能就是使用这种方式。先看程序代码来说明
Task<int> task=Task.Run(
()=>Enumerable.Range(1,5000000).Count(n=>(n%3)==0);
var awaiter=task.GetAwaiter();
awaiter.OnCompleted(()=>
{
int result=awaiter.GetResult();
Console.WriteLine("整除3的个数有:"+Result);
Console.WriteLine("Task执行中...")
Console.ReadLine();
});
调用Task.GetAwaiter方法会返回一个awaiter(等待者)对象,它会让先前的Task在完成或出错之后执行一个委派,如果先前的Task出现错误,那么当接续工作调用awaited.GetResult()时就会弹出例外。使用GetResult的好处是,当先前的Task出错时,例外可以直接弹出,而不会封装在 aggregateException中。
Task.Delay方法在建立时间延迟之后才会完成的工作,简单地说,它就是Thread.Sleep的异步版本
Parallel类 主要提供并行循环和区域的支持,主要有三个方法For、Foreach、Invoke,主要说一下Invoke方法,Invoke方法会简化启动一组并行操作,它接受一组Action[]数组。
- Async与await关键词 有async修饰词不一定有await运算符,但有await运算符一定有async修饰词,按照微软的建议,开发人员自己编写的异步方法最好也采用“Async”结尾。
- 异步与线程池
当在应用程序中大量使用多线程技术时,首先有一点必须注意。.net framework 会在IIS维护一个线程池,当iis接受到一个请求时,就会从线程池读取一个线程处理的请求,如果使用同步处理程序方式处理此请求,此线程直到程序处理完毕之前,都会一直为自己请求服务,并且此线程无法再为其他请求进行服务,就是所谓的封锁。 如果线程池够大,就没什么问题,不过线程池的数量是有限的,当有大量突发请求,或可应请求线程都忙于后端高延迟性网络任务时,所有线程池的线程都被封锁,这种情况称为线程耗尽。发生线程耗尽,IIS会开始将请求排入队列,如果队列已满时,IIS就会拒绝请求,并呈现http503状态,如果异步应用程序正常,但是偶尔会发生http503状态,那么可以开课,事故为县城靠近的情况,可以试着调高队列长度,例如调高至一千。 - 多线程与异步
Task->新线程->读取网络资源
多线程只是换一条线程来代替原本会被锁定的主线程,也就是用线程资源(CPU与内存)换可响应的接口。如果换成异步呢?
异步->线程池->读取网络资源
在线程池中时使用DMA(Direct Memory Access,直接内存访问)模式,直接内存访问是一种不经过CPU而直接进行内存数据读取的机制。CLR提供的异步程序模型就是让开发者充分利用DMA功能来降低CPU压力
所以结论是:
-CPU Bound:适合采用多线程;
-IO Bound:适合采用异步;
下一步是时候看看 .Net Core了。