Asynchronous programming has received a lot of attention in recent years, mainly for two key reasons: First, it helps provide a better user experience because it does not block the UI thread and avoids UI interface hangs before the end of processing. rise. Second, it helps scale the system significantly without adding additional hardware.
However, writing proper asynchronous code to manage threads itself is a tedious job. Nonetheless, its huge benefits have led many new and old technologies to start using asynchronous programming. Microsoft has also invested a lot in .NET 4.0 since it was released, and subsequently introduced the async and await keywords in .NET 4.5, making asynchronous programming easier than ever.
However, async functionality in ASP.NET has been available since the beginning, it just never got the attention it deserved. And, given the way ASP.NET and IIS handle requests, the advantages of asynchronous implementation may be even greater. Through asynchronous, we can easily greatly improve the scalability of ASP.NET applications. As new programming constructs are introduced, such as the async and await keywords, we should also learn to use the power of asynchronous programming.
In this blog post, we will discuss how IIS and ASP.NET handle requests, then look at where asynchronous can be used in ASP.NET, and finally discuss a few scenarios where the advantages of asynchronous are best demonstrated.
How are requests handled?
Every ASP.NET request goes through IIS before being finally processed by the ASP.NET handler. First, IIS receives the request, and after preliminary processing, sends it to ASP.NET (it must be an ASP.NET request), and then ASP.NET actually processes it and generates a response, and then the response is sent back to the client through IIS. On IIS, there are some worker processes that are responsible for dequeuing the request, executing the IIS module, and then sending the request to the ASP.NET queue. However, ASP.NET itself does not create any threads, nor does it have a thread pool to process requests. Instead, it uses the CLR thread pool to obtain threads from it to process requests. Therefore, the IIS module calls ThreadPool.QueueUserWorkItem to queue the request for processing by the CLR worker thread. We all know that the CLR thread pool is managed by the CLR and can be automatically adjusted (that is, it creates and destroys processes as needed). Also remember here that creating and destroying threads is a heavy task, which is why the CLR thread pool allows the same thread to be used for multiple tasks. Let's look at a diagram describing the request processing process.
As you can see in the above image, the request is first received by HTTP.sys and added to the corresponding kernel-level application pool queue. Then, an IIS worker thread takes the request from the queue, processes it and passes it to the ASP.NET queue. Note that if the request is not an ASP.NET request, it will be automatically returned from IIS. Finally, a thread is allocated from the CLR thread pool to handle the request.
What are the usage scenarios of asynchronous in ASP.NET?
All requests can be roughly divided into two categories:
CPU Bound class
I/O Bound class
CPU Bound class requests require CPU time and are executed in the same process; while I/O Bound class requests, It is inherently blocking and needs to rely on other modules to perform I/O operations and return responses. Blocking requests are a major obstacle to improving application scalability, and in most web applications, a lot of time is wasted waiting for I/O operations. Therefore, the following scenarios are suitable for using asynchronous:
I/O Bound class requests, including:
Database access
Read/write files
Web service calls
Access network resources
Event-driven requests, such as SignalR
Required Scenarios for obtaining data from multiple data sources
As an example, here is a simple synchronous page created and then converted into an asynchronous page. This example sets a delay of 1000ms (to simulate some heavy database or web service calls, etc.), and also uses WebClient to download a page, as shown below:
protected void Page_Load(object sender, EventArgs e) { System.Threading.Thread.Sleep(1000); WebClient client = new WebClient(); string downloadedContent = client.DownloadString("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx"); dvcontainer.InnerHtml = downloadedContent; }
Now convert the page into an asynchronous page, there are three main steps involved here Steps:
Add Async = true in the page command to convert the page into an asynchronous page, as shown below:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="AsyncTest.Home" Async="true" AsyncTimeout="3000" %>
AsyncTimeout (optional) is also added here, please select according to your needs.
2.将此方法转换成异步方法。在这里把Thread.Sleep 与 client.DownloadString 转换成异步方法如下所示:
private async Task AsyncWork() { await Task.Delay(1000); WebClient client = new WebClient(); string downloadedContent = await client.DownloadStringTaskAsync("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx "); dvcontainer.InnerHtml = downloadedContent; }
3.现在可以直接在 Page_Load (页面加载)上调用此方法,使其异步,如下所示:
protected async void Page_Load(object sender, EventArgs e) { await AsyncWork(); }
但是这里的 Page_Load 返回的类型是async void,这种情况无论如何都应该避免。我们知道,Page_Load 是整个页面生命周期的一部分,如果我们把它设置成异步,可能会出现一些异常情况和事件,比如生命周期已经执行完毕而页面加载仍在运行。 因此,强烈建议大家使用 RegisterAsyncTask 方法注册异步任务,这些异步任务会在生命周期的恰当时间执行,可以避免出现任何问题。
protected void Page_Load(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(AsyncWork)); }
现在,页面已经转换成了异步页,它就不再是一个阻塞性请求。
笔者在 IIS8.5 上部署了同步页面和异步页面,并使用突发负载对两者进行了测试。测试结果发现,相同的机器配置,同步页面在2-3秒内只能提取1000个请求,而异步页面能够为2200多个请求提供服务。此后,开始收到超时(Timeout)或服务器不可用(Server Not Available)的错误。虽然两者的平均请求处理时间没有多大差别,但是通过异步页面,可以处理两倍以上的请求。这足以证明异步编程功能强大,所以应该充分利用它的优势。
ASP.NET中还有几个地方也可以引入异步:
编写异步模块
使用IHttpAsyncHandler 或 HttpTaskAsyncHandler 编写异步HTTP处理程序
使用web sockets 或 SignalR
结论
本篇博文中,我们讨论了异步编程,而且发现,新推出的async 和 await关键字,使异步编程变得十分简单。我们讨论的话题包括 IIS和ASP.NET如何处理请求,以及在哪些场景中异步的作用最明显。另外,我们还创建了一个简单示例,讨论了异步页面的优势。最后我们还补充了几个ASP.NET中可以使用异步的地方。