This article describes how to use an asynchronous method to eliminate the performance problems of web service calls using Microsoft ASP. NET and the consumption of thread pool resources.
Situation: Performance damage when Web services are called from ASP. NET pages
When we discuss web services in this article, we hope to enjoy web services in various situations. One major case is to access the Web service from an intermediate layer environment (such as an ASP. NET web page. People who provide support for users of the mappoint. NET web service often receive this problem, that is, when users use their web service, the call to mappoint. NET may take a long time. This is not a problem in itself, but some other factors can make it a much more serious problem than on the surface.
HTTP dual-connection restrictions
The HTTP specification indicates that an HTTP client and any server can establish up to two TCP connections at the same time. This prevents a single browser from over-loading the Server due to excessive connection requests when browsing a page (for example, with 120 embedded thumbnails. At this time, the browser will create only two connections, and then start to send 120 HTTP requests through the two pipelines, instead of creating 120 TCP connections and sending HTTP requests through each connection. The problem with this method is that the middle layer may have 50 users requesting connection at the same time. If you have to make a mappoint. NET web service call for each user, 48 users will wait for one of the two pipelines to be idle.
Thread Pool restrictions
ASP. NET processes incoming requests by providing services through a group of threads called the process thread pool. Normally, after a request is passed in, a idle thread in the pool will provide services for it. The problem here is that the process thread pool does not create countless threads to process a large number of requests. It is a good thing to have the maximum number of threads, because if we create threads infinitely, all the resources on the computer can only be used to manage these threads. By limiting the number of threads that can be created, we can keep the system overhead of thread management at a controllable level. If all threads in the thread pool are occupied when a request is passed in, the request will be queued. After the busy thread completes the task, the idle thread can process the new request. This method is actually more effective than switching to a new thread because thread switching between requests is not required. However, if the thread usage efficiency is not high (especially on a very busy Web Server), the waiting Request queue will become very large.
Consider the Web service calling from the ASP. NET page. If synchronous calling is performed, the running thread is blocked until the Web service call is completed. During the call, the thread cannot perform any other activity. It cannot process other requests and can only wait. If a single processor computer has a default number of worker threads of 20, you only need 20 requests simultaneously to use up all the threads. Future requests must wait in queue.
This issue is not limited to Web Services.
Not only do users who call web services encounter congestion and time-consuming problems when calling from web pages. The same problem occurs when calling any number of long requests, for example, SQL Server? Requests, reading or writing long files, various Web requests, or accessing a concurrent Resource (locking may cause serious latency ). In fact, there are many cases where Web services are used, and the service calls are fast, which is not a problem. However, you may understand that if you want to use the proxy server to call mappoint. net web service, the connection used has a certain delay, and the corresponding service may take some time to process the request, then you may see the delay everywhere, if the site is busy, problems may occur.
Problem Improvement
Some aspects of this problem can be improved through some configuration settings for the environment. Let's take a look at some configuration settings that can be used to improve the problem.
Maxconnections
The default dual-connection restriction for connecting to web resources can be controlled through a configuration element named connectionmanagement. The connectionmanagement settings allow you to add the names of sites that require non-default connection restrictions. You can add the following content to a typical web. config file and increase the default connection limit of all servers you connect to 40.
<Configuration> <System.net> <Connectionmanagement> <Add address = "*" maxconnection = "40"/> </Connectionmanagement> </System.net> <System. Web> ... |
It should be noted that there is no limit on the number of connections to the local computer. Therefore, if you are connecting to the local host, this setting is invalid.
Maxworkerthreads and minfreethreads |
If an HTTP 503 error is received ("temporary service overload"), it indicates that all threads in the thread pool are occupied and the request queue has exceeded the maximum value (apprequestqueuelimit is set to 100 by default ). For IIS 5.0 installation, you can simply increase the size of the thread pool. For IIS 6.0 installation (incompatible with IIS 5.0), these settings are invalid.
Maxworkerthreads and maxiothreads control the number of worker threads and the number of threads that process newly submitted ASP. NET requests respectively. These settings need to be configured in your machine. config, which will affect all web applications running on your computerProgram. Maxworkerthreads is part of the processmodel element in machine. config. After you view it, you will find that the default value of this setting is 20 threads per processor.
Minfreethreads settings can be configured in machine. config, or in the httpruntime element in the web. config file of your application. When the number of Idle threads is lower than the configured limit, it is forbidden to use threads in the thread pool to process incoming HTTP requests. This is useful if you need a thread pool thread to complete the pending request. If all threads are used to process incoming HTTP requests, and these requests are waiting for another thread to complete processing, they will enter the Deadlock State. For example, if you are calling an asynchronous web service from an ASP. NET application and waiting for the callback function to complete the request, this situation occurs. Because the callback must be performed on Idle threads in the process thread pool. If you want to view your machine. config, you will notice that the default value set by minfreethreads is 8. If the limit of the worker thread pool is 20, the default value can also meet the requirements. However, if the thread pool size increases to 100, the default value is too small.
It should be noted that if your ASP. NET application calls web services on the local computer, the issue of thread pool restrictions will be intensified. For example, the test application I created for this column calls a web service on the same computer as the ASPX page. Therefore, for blocked calls, a thread is used for both ASPX page and asmx Web Service requests. This effectively doubles the number of concurrent requests processed by the Web server. When two web service requests (called using asynchronous Web Service) are simultaneously executed, we eventually increase the number of requests simultaneously by two times. To avoid this type of problem when you call back a local computer, you should consider the architecture of your application so that it can be directly removed from the aspxCodeTo execute the code in the web method.
Windows XP restrictions
We must note that if you are in a windows? If you perform a test on an XP Computer, another restriction is that the XP Web Server manually limits the number of concurrent connections allowed. Because Windows XP is not a server platform, its concurrent connections are limited to 10. This is usually normal for testing in the development environment, but if you try to perform any complex tests, this restriction problem will be more serious. The connection to the local computer is not affected by this restriction.
Real solution: asynchronous request processing
Adjusting configuration settings is a way to improve the problem, while it is another way to completely solve the problem when designing a web application. Threads waiting for the completion of the blocked call will never have better room for adjustment. Therefore, the solution is to completely avoid the blocking problem. Asynchronous request processing is an appropriate solution. This is manifested in two aspects: asynchronous web service calling and asynchronous processing of requests in ASP. NET web applications.
Asynchronous web service call
In my previous column, I wrote questions about asynchronous calling of Web Services. Enabling threads without waiting for the completion of web service calls is a key part of the asynchronous page processing model for creating release threads to process more requests. In addition, asynchronous calling of Web Services is also relatively simple.
Consider the following Visual Basic. Net code for the ASPX page:
'poor performance caused by incorrect calls to synchronous Web Services 'page! public class syncpage inherits system. web. UI. page protected withevents label1 as system. web. UI. webcontrols. label protected withevents label2 as system. web. UI. webcontrols. label private sub page_load (byval sender as system. object, _ byval e as system. eventargs) handles mybase. load 'Call the Web Service dim proxy as new localhost. service1 label1.text = proxy. method1 (500) label2.text = proxy. method1 (200) end sub end class |
This code is easy to understand. When the page is loaded, a Web Service proxy instance is created, and then the instance is used to call a web method named Method1 twice. Method1 returns only strings containing input parameters passed to this method. To add a certain degree of latency to the system, Method1 sleep for 3 seconds before returning the string. The string returned from the call to Method1 is placed in the text of two labels on the ASPX page. This page provides very poor performance and absorbs threads from the process thread pool like a sponge. Because the Method1 web method has a latency of 3 seconds, it takes at least 6 seconds to call the page.
The following code snippet shows a code similar to a Web page, but now it is called by asynchronous Web Services.
Public class asyncpage Inherits system. Web. UI. Page Protected withevents label1 as system. Web. UI. webcontrols. Label Protected withevents label2 as system. Web. UI. webcontrols. Label Private sub page_load (byval sender as system. Object ,_ Byval e as system. eventargs) handles mybase. Load 'Call the Web Service Dim proxy as new localhost. service1 Dim res as iasyncresult = Proxy. beginmethod1 (500, nothing, nothing) Dim RES2 as iasyncresult = Proxy. beginmethod1 (200, nothing, nothing) Label1.text = proxy. endmethod1 (RES) Label2.text = proxy. endmethod1 (RES2) End sub End Class |
Similarly, this page creates a Web Service proxy and then calls the Method1 web method twice. The difference is that beginmethod1 is called instead of Method1. Beginmethod1 call will return immediately, so that we can start to call this method for the second time. Different from waiting for the first web service call to complete in the first example, we can start both calls at the same time. Calls to endmethod1 only cause blocking before a specific call is completed.
It is worth noting that when we return from the ASPX page, the response will be sent to the client. Therefore, we cannot return data from the page_load method before obtaining the required data. This is why we need to block web service calls until they are completed. The good thing is that two calls can be executed at the same time, so the latency of the previous 6 seconds will now be reduced to about 3 seconds. Although this is better, a blocked thread is still created. What we really need is to release threads while completing web service calls so that they can process HTTP requests. The problem is that the ASPX page processing model does not have an asynchronous execution mode. However, ASP. NET does provide a solution to this problem.
Asynchronous prerequesthandler execution
ASP. NET supports classes called httphandlers. Httphandlers is a class that implements the ihttphandler interface and is used to provide services for HTTP requests to files with a specific extension. For example, if you look at the machine. config file, you will notice that many httphandlers are used to request files with extensions (such as. asmx,. aspx,. ashx, And. config. For a request with a specific extension, ASP. NET will view its configuration information, and then call the httphandler associated with it to provide services for the request.
ASP. NET also supports writing Event Handlers. Such events can occur at all times during HTTP request processing. One of these events is the prerequesthandlerexecute event, which happens just before the httphandler of a specific request is called. An asynchronous support for prerequesthandlerexecute notifications can be registered to use the addonprerequesthandlerexecuteasync method of the httpapplication class. The httpapplication class is derived from the event handler created based on the Global. asax file. We will use the asynchronous prerequesthandler option to provide the asynchronous Execution Mode for Web service calls.
The first thing to do before calling addonprerequesthandlerexecuteasync is to create a begineventhandler and an endeventhandler function. After the request is passed in, the begineventhandler function is called. We will start asynchronous web service calling at this time. Begineventhandler must return an iasyncresult interface. If you are calling a web service, you can only return the iasyncresult interface returned by the begin function of the web service (in our example, an iasyncresult interface will be returned by the beginmethod1 method ). In the example I created, I want to perform the same operations as the previous web page example (which reveals synchronous and asynchronous web service calls. This means that I have to create my own iasyncresult interface. My begineventhandler code is as follows:
Public Function beginprerequesthandlerexecute ( Byval sender as object ,_ Byval e as eventargs ,_ Byval CB as asynccallback ,_ Byval extradata as object) as iasyncresult If request. url. absolutepath _ = "/Webapp/prerequesthandlerpage. aspx" then Dim proxy as myproxy = new myproxy Proxy. Res = new myasyncresult Proxy. res. result1 = Proxy. beginmethod1 (_ 500 ,_ New asynccallback (addressof mycallback ),_ Proxy) Proxy. res. result2 = Proxy. beginmethod1 (_ 300 ,_ New asynccallback (addressof mycallback ),_ Proxy) Proxy. res. Callback = CB Proxy. res. State = extradata Proxy. res. Proxy = proxy Return proxy. Res End if Return new myasyncresult End Function |
There are many interesting things to note about this code. First, this code is called for each HTTP request processed for this virtual directory. Therefore, the first thing I do is to check the actual Request Path and check whether it is the path of the page for which I want to provide services.
My function uses some interesting input parameters for calling. The CB parameter is the callback function passed to me by ASP. NET. ASP. NET can call the callback function provided to me after my asynchronous work is completed. They know when to call my endeventhandler in this way. Similarly, if I only call one web service, I only need to pass the callback to beginmethod1, and then the Web service call will call the function. However, in this example, I have made two separate calls. Therefore, I have created an intermediate callback function that is passed to two beginmethod1 calls and checked whether both calls have been completed in the callback code. If it is not completed, I will return it; if it is completed, I will call the original callback. Another interesting parameter is the extradata parameter, which saves the status for ASP. NET when ASP. NET calls me. This status information must be returned when I call a callback function specified by the CB parameter. Therefore, I store it in the created iasyncresult class. My callback code is as follows:
Public sub mycallback (byval Ar as iasyncresult) Dim proxy as myproxy = ar. asyncstate If proxy. res. iscompleted then Proxy. res. Callback. Invoke (proxy. Res) End if End sub |
It should also be mentioned that the class I created to implement iasyncresult (called myasyncresult) will check the completion of two pending web service calls when querying the iscompleted attribute.
In endeventhandler, I just get the result from a Web service call and store it in the current request context. The context is the same as the context to be passed to httphandler. In this example, it is the. aspx request handler so that it can be used for my standard code. My endeventhandler code is as follows:
Public sub endprerequesthandlerexecute (byval Ar as iasyncresult) If request. url. absolutepath _ = "/Webapp/prerequesthandlerpage. aspx" then Dim res as myasyncresult = ar Dim proxy as myproxy = res. Proxy Dim retstring as string Retstring = proxy. endmethod1 (proxy. res. result1) Context. Items. Add ("webserviceresult1", retstring) Retstring = proxy. endmethod1 (proxy. res. result2) Context. Items. Add ("webserviceresult2", retstring) End if End sub |
Because the data on the. ASPX page has been received, the actual page processing is very simple.
public class prerequesthandlerpage inherits system. web. UI. page protected withevents label1 as system. web. UI. webcontrols. label protected withevents label2 as system. web. UI. webcontrols. label private sub page_load (byval sender as system. object, _ byval e as system. eventargs) handles mybase. load label1.text = context. items ("webserviceresult1") label2.text = context. items ("webserviceresult2") end sub end class |
This is not just theory-it does work!
Without considering that I have not blocked all threads, at least less resources are wasted, which makes sense. But will the actual results be different? The answer is yes "! I put the three testing cases described in this column together: Two blocked calls from the web page code and two asynchronous calls from the web page code, and two asynchronous calls from the prerequesthandler code. I tested these three cases using Microsoft Application Center Test and sent requests continuously from 100 virtual clients within 60 seconds. The displayed result indicates the number of requests completed in 60 seconds.
Figure 1: requests completed by clients simultaneously within 60 seconds
The number of requests processed by the asynchronous prerequesthandler method is approximately eight times the number of requests processed by the second method. Therefore, this method allows you to process more requests. But how long does it take to complete a single request? Shows the average response time of the three methods.
Figure 2: average completion response time of clients simultaneously requesting
The average request response time using the prerequesthandler method is only 3.2 seconds. Assuming that the built-in latency of each web service call is 3 seconds, this method is a very effective solution.
I must point out that these non-scientific figures are obtained from computers that run in my non-scientific office, not on scientific computers. Of course, it makes sense to release Idle threads and let them do some practical work to improve performance. We hope these results can indicate that the performance improvement is very significant.
The prerequesthandler method is necessary because the. aspx request handler does not have a built-in asynchronous request processing mechanism. But not all ASP. net http handlers are like this. The prerequesthandler method is applicable to all ASP. NET Request types, but it is easier to use a programming method that puts asynchronous support within the. asmx handler than to use the prerequesthandler programming method.
Summary
The asynchronous execution model is a good method whenever any type of process has a long performance problem. When a Web Service is called from the. ASPX page, we believe that asynchronous web service calls can be combined with the asynchronous execution mode provided by ASP. NET. This solves the problem of lack of asynchronous support in the process of processing. aspx requests. This asynchronous method can eliminate performance problems and thread pool resource consumption problems.