我一直在这里阅读一些 async
文章:http://www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45,作者说:
当您进行异步工作时,您并不总是使用线程 . 例如,当您发出异步Web服务请求时,ASP.NET将不会在异步方法调用和await之间使用任何线程 .
所以我想要理解的是,如果我们不使用任何线程进行并发执行,它将如何成为 async
? "you're not always using a thread."是什么意思?
让我首先解释一下我对使用线程的了解(一个简单的例子,当然Threads可以在UI和Worker方法之外的其他情况下使用)
-
你有UI线程来接受输入,给出输出 .
-
您可以处理UI线程中的内容,但它会使UI无响应 .
-
因此,假设我们有一个与流相关的操作,我们需要下载某种数据 .
-
我们还允许用户在下载时执行其他操作 .
-
我们创建了一个新的工作线程,用于下载文件并更改进度条 .
-
一旦完成,没有任何事情要做,因此线程被杀死 .
-
我们继续从UI线程 .
我们可以根据情况等待UI线程中的工作线程,但在下载文件之前,我们可以使用UI线程做其他事情,然后等待工作线程 .
async
编程不一样吗?如果没有,有什么区别?我读到 async
编程使用 ThreadPool
从中拉出线程 .
3 回答
异步编程不需要线程 .
"Asynchronous"表示API不会阻止调用线程 . 这并不意味着有另一个阻塞的线程 .
首先,考虑一下您的UI示例,这次使用实际的异步API:
你有UI线程来接受输入,给出输出 .
您可以处理UI线程中的内容,但它会使UI无响应 .
因此,假设我们有一个与流相关的操作,我们需要下载某种数据 .
我们还允许用户在下载时执行其他操作 .
We use asynchronous APIs to download the file. 无需工作线程 .
异步操作将其进度报告回UI线程(更新进度条),并且还将其完成报告给UI线程(可以像任何其他事件一样响应它) .
这显示了如何只涉及一个线程(UI线程),还有异步操作 . 您可以启动多个异步操作,但这些操作只涉及一个线程 - 没有线程被阻塞 .
async
/await
提供了一个非常好的语法,用于启动异步操作然后返回,并在该操作完成时继续该方法的其余部分 .ASP.NET类似,但它没有主/ UI线程 . 相反,它为每个未完成的请求都有一个“请求上下文” . ASP.NET线程来自线程池,当它们处理请求时,它们进入“请求上下文”;当他们完成后,他们退出他们的“请求上下文”并返回到线程池 .
ASP.NET会跟踪每个请求的不完整异步操作,因此当线程返回到线程池时,它会检查该请求是否正在进行任何异步操作;如果没有,则请求完成 .
因此,当您在ASP.NET中执行不完整的异步操作时,该线程将递增该计数器并返回 . ASP.NET知道请求isn 't complete because the counter is non-zero, so it doesn' t完成响应 . 线程返回到线程池,此时:没有线程处理该请求 .
异步操作完成后,它会将
async
方法的其余部分调度到请求上下文 . ASP.NET获取其处理程序线程之一(可能是也可能不是执行async
方法的早期部分的同一线程),计数器递减,并且线程执行async
方法 .ASP.NET vNext略有不同;整个框架中对异步处理程序的支持更多 . 但一般概念是一样的 .
欲获得更多信息:
My async/await intro post试图同时成为
async
和await
如何工作的介绍还是相当完整的图片 .official async/await FAQ有很多很棒的链接,详细介绍了很多 .
MSDN杂志的文章It's All About the SynchronizationContext暴露了一些下面的管道 .
我第一次看到 async 和 await 时,我认为它们是异步编程模型的C#语法糖 . 我错了, async 和 await 不止于此 . 它是一个全新的异步模式基于任务的异步模式,http://www.microsoft.com/en-us/download/details.aspx?id=19957是一篇很好的文章来开始 . 大多数使用TAP的FCL类都是调用APM方法(BegingXXX()和EndXXX()) . 以下是TAP和AMP的两个代码段:
TAP样本:
APM样本:
最后这两个将是相同的,因为GetResponseAsync()调用BeginGetResponse()和EndGetResponse() . 当我们反映GetResponseAsync()的源代码时,我们将获得如下代码:
对于APM,在BeginXXX()中,有一个回调方法的参数,该方法将在任务(通常是IO繁重操作)完成时调用 . 创建一个新的线程并异步,它们都将立即在主线程中返回,它们都被解除阻塞 . 在性能方面,创建新线程会在进程I / O绑定操作(例如读取文件,数据库操作和网络读取)时花费更多资源 . 创建新线程有两个缺点,
就像你提到的文章一样,有内存成本和CLR
线程池的限制 .
将发生上下文切换 . 另一方面,异步不会手动创建任何线程,并且当IO绑定操作返回时它不会有上下文切换 .
这是一张有助于理解差异的图片:
此图来自MSDN文章“Asynchronous Pages in ASP.NET 2.0”,它详细解释了旧的异步如何在ASP.NET 2.0中工作 .
关于异步编程模型,请参阅第27章中Jeffrey Richter的文章“Implementing the CLR Asynchronous Programming Model ", also there are more detail on his book " CLR via Csharp 3rd Edition” .
让我们假设您正在实现一个Web应用程序,并且当每个客户端请求进入您的服务器时,您需要发出数据库请求 . 当客户端请求进入时,线程池线程将调用您的代码 . 如果现在同步发出数据库请求,则线程将无限期地阻塞,等待数据库响应结果 . 如果在此期间有另一个客户端请求进入,则线程池必须创建另一个线程,并且当该线程发出另一个数据库请求时,该线程将再次阻塞 . 随着越来越多的客户端请求进入,越来越多的线程被创建,所有这些线程阻塞等待数据库响应 . 结果是您的Web服务器正在分配许多甚至几乎不使用的系统资源(线程及其内存)!更糟糕的是,当数据库回复各种结果时,线程会被解除阻塞,并且它们都会开始执行 . 但是,由于您可能运行了大量线程且CPU核心相对较少,因此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 []中的字节之后,你不能拥有代码 .