asynchronous编程与线程有什么不同?
我一直在阅读一些async
文章: http : //www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45和作者说:
当你在做asynchronous工作时,你并不总是使用一个线程。 例如,当您发出asynchronousWeb服务请求时,ASP.NET将不会在async方法调用和await之间使用任何线程。
所以我想了解的是,如果我们不使用任何线程来执行并发,它将如何变成async
? 这是什么意思“你不总是使用一个线程”?
让我首先解释一下我所知道的有关使用线程的知识(一个简单的例子,当然,除了UI和Worker方法之外,线程可以用于不同的情况)
- 你有UI线程input输出。
- 您可以在UI线程中处理事情,但它会使UI无响应。
- 因此,让我们说我们有一个stream相关的操作,我们需要下载一些数据。
- 而且我们还允许用户在下载时做其他事情。
- 我们创build一个新的工作线程,下载文件并更改进度条。
- 一旦完成,就没有什么可做的,所以线程被杀死了。
- 我们继续从UI线程。
我们可以根据情况等待UI线程中的工作线程,但在此之前,在下载文件的同时,我们可以使用UI线程做其他事情,然后等待工作线程。
async
编程不一样吗? 如果不是,有什么区别? 我读了async
编程使用ThreadPool
从拉线虽然。
线程对于asynchronous编程不是必需的。
“asynchronous”意味着API不会阻塞调用线程。 这并不意味着有另一个阻塞的线程。
首先,考虑你的UI示例,这次使用实际的asynchronousAPI:
- 你有UI线程input输出。
- 您可以在UI线程中处理事情,但它会使UI无响应。
- 因此,让我们说我们有一个stream相关的操作,我们需要下载一些数据。
- 而且我们还允许用户在下载时做其他事情。
- 我们使用asynchronousAPI来下载文件。 没有工作者线程是必要的。
- asynchronous操作将其进度报告回UI线程(更新进度条),并将其完成报告给UI线程(可以像任何其他事件一样对其进行响应)。
这显示了如何可以只有一个线程(UI线程),但也有asynchronous操作。 你可以启动多个asynchronous操作,但只有一个线程涉及这些操作 – 没有线程被阻塞。
async
/ await
提供了一个非常好的语法来启动一个asynchronous操作,然后返回,并在该操作完成时让该方法的其余部分继续。
ASP.NET是相似的,除了它没有主/ UI线程。 相反,它对每个不完整的请求都有一个“请求上下文”。 ASP.NET线程来自一个线程池,当他们处理一个请求时,他们进入“请求上下文” 当他们完成后,他们退出他们的“请求上下文”并返回到线程池。
ASP.NET会跟踪每个请求的不完整asynchronous操作,因此当线程返回到线程池时,它将检查是否有任何正在进行的asynchronous操作正在进行。 如果没有,则请求完成。
因此,当您在ASP.NET中await
不完整的asynchronous操作时,线程将递增该计数器并返回。 ASP.NET知道请求没有完成,因为计数器是非零的,所以它没有完成响应。 线程返回到线程池,并在那个点: 没有线程在该请求上工作。
当asynchronous操作完成时,它会将async
方法的其余部分调度到请求上下文。 ASP.NET抓取它的一个处理线程(可能是也可能不是执行async
方法的早期部分的那个线程),计数器递减,并且线程执行async
方法。
ASP.NET vNext略有不同; 在整个框架中对asynchronous处理程序有更多的支持。 但总的概念是一样的。
了解更多信息:
- 我的asynchronous/等待介绍帖尝试既是一个简介 ,也是如何合理完整的
async
和await
工作的图片。 - 官方的asynchronous/等待常见问题有许多伟大的联系,进入了很多的细节。
- MSDN杂志文章“所有关于SynchronizationContext”展示了下面的一些pipe道。
第一次当我看到asynchronous和等待 ,我认为他们是C#语法糖的asynchronous编程模型。 我错了, asynchronous和等待不止于此 。 这是一个全新的asynchronous模式基于任务的asynchronous模式, http://www.microsoft.com/en-us/download/details.aspx?id=19957是一个很好的文章开始。; 大多数实现TAP的FCL类是调用APM方法(BegingXXX()和EndXXX())。 以下是TAP和AMP的两个代码捕捉:
TAP样本:
static void Main(string[] args) { GetResponse(); Console.ReadLine(); } private static async Task<WebResponse> GetResponse() { var webRequest = WebRequest.Create("http://www.google.com"); Task<WebResponse> response = webRequest.GetResponseAsync(); Console.WriteLine(new StreamReader(response.Result.GetResponseStream()).ReadToEnd()); return response.Result; }
APM样本:
static void Main(string[] args) { var webRequest = WebRequest.Create("http://www.google.com"); webRequest.BeginGetResponse(EndResponse, webRequest); Console.ReadLine(); } static void EndResponse(IAsyncResult result) { var webRequest = (WebRequest) result.AsyncState; var response = webRequest.EndGetResponse(result); Console.WriteLine(new StreamReader(response.GetResponseStream()).ReadToEnd()); }
最后这两个将是一样的,因为GetResponseAsync()里面调用了BeginGetResponse()和EndGetResponse()。 当我们reflectionGetResponseAsync()的源代码时,我们将得到如下的代码:
task = Task<WebResponse>.Factory.FromAsync( new Func<AsyncCallback, object, IAsyncResult>(this.BeginGetResponse), new Func<IAsyncResult, WebResponse>(this.EndGetResponse), null);
对于APM,在BeginXXX()中,有一个callback方法的参数,当任务(通常是IO繁重的操作)完成时将调用该方法。 创build一个新的线程和asynchronous,他们都将立即在主线程返回,他们都畅通。 在性能方面,当处理I / O绑定的操作,例如读取文件,数据库操作和networking读取时,创build新线程将花费更多的资源。 创build新线程有两个缺点,
- 就像在你提到的文章中,有内存成本和CLR是
线程池的限制。 - 上下文切换将发生。 另一方面,asynchronous不会手动创build任何线程,并且在IO绑定操作返回时不会有上下文切换。
这里有一张照片可以帮助你理解这些差异:
此图来自MSDN文章“ ASP.NET 2.0中的asynchronous页面 ”,它解释了有关ASP.NET 2.0中的旧asynchronous工作的详细信息。
关于asynchronous编程模型,请从Jeffrey Richter的文章“ 实现CLRasynchronous编程模型 ”中获得更多详细信息,第27章中有关他的书“CLR via Csharp 3rd Edition”的更多详细信息。
假设您正在实现一个Web应用程序,并且每个客户端请求进入您的服务器,您都需要提出一个数据库请求。 当客户端请求进来时,一个线程池线程会调用你的代码。 如果现在同步发出数据库请求,则线程将阻塞无限期的时间,等待数据库响应结果。 如果在此期间有另一个客户端请求进入,线程池将不得不创build另一个线程,并且当它发出另一个数据库请求时,该线程再次被阻塞。 随着越来越多的客户端请求进入,越来越多的线程被创build,并且所有这些线程阻塞等待数据库响应。 结果就是你的Web服务器正在分配很多系统资源(线程和内存),这些资源几乎没有被使用! 更糟的是,当数据库确实回复了各种结果时,线程变得畅通无阻,它们都开始执行。 但是由于你可能有很multithreading在运行,并且CPU核心相对较less,所以Windows必须频繁地执行上下文切换,这更加伤害了性能。 这不是实现可伸缩应用程序的方法。
要从文件读取数据,我现在调用ReadAsync而不是Read。 ReadAsync内部分配一个Task对象来表示读取操作的挂起完成。 然后,ReadAsync调用Win32的ReadFile函数(#1)。 ReadFile分配它的IRP,就像它在同步场景(#2)中那样进行初始化,然后把它传递给Windows内核(#3)。 Windows将IRP添加到硬盘驱动器的IRP队列(#4),但是现在,不是阻塞线程,而是允许线程返回到您的代码; 您的线程立即从其调用返回到ReadAsync(#5,#6和#7)。 现在,IRP当然还没有被处理,所以你不能在ReadAsync之后的代码尝试访问传入的Byte []中的字节。