Asynchronous Pages


This section covers the @Page directive's Async attribute, and looks at the changes it makes to the flow of a page and how it alters the page to integrate with the asynchronous task invocation models discussed earlier.

Async="true"

In order for our asynchronous tasks to work, we had to mark our @Page directive with the Async attribute. If you set this attribute to true on a page that does not register any tasks or invoke any asynchronous Web service proxies, it will actually not have much effect on the running of the page. The page will be processed on the same thread throughout its lifecycle, much like any other page in ASP.NET. If, however, you have registered one or more asynchronous tasks or have invoked one or more Web service proxies asynchronously, then the page will be executed asynchronously.

When you mark a page with Async="true", it does three important things. First, it explicitly implements the IHttpAsyncHandler interface on your generated Page-derived class. Next, it implements that interface's BeginProcessRequest/EndProcessRequest methods to map onto the Page class' AsyncPageBeginProcessRequest/AsyncPageEndProcessRequest helper methods respectively. Finally, it sets the AsyncMode property of the Page-derived class to true in its constructor. This last point is important if you are ever authoring controls or Web parts that may be deployed in both synchronous and asynchronous pages. It lets you check to see whether your containing page is in fact asynchronous or not and adjust your data retrieval as needed.

Once a page has been marked as asynchronous, the most important change to you (the developer) is the page's flow of control. The page will begin execution on the standard request thread, processing the page lifecycle all the way through the PreRender event, as shown in Figure 9-6.

Figure 9-6. Asynchronous page flow of control


Relaxing Thread-Pool Pressure

When the page is marked as asynchronous and the bulk of the page's work has been pushed into asynchronous I/O-bound tasks, it means that the thread pool will be used much more efficiently. Instead of each request holding onto a thread waiting for data to return from I/O operations, the thread is relinquished back to the thread pool while the I/O operations execute asynchronously without any overhead of thread allocation. Once the final I/O operation completes, the I/O thread-pool thread that is used to harvest the results of the I/O request continues the execution of the page, including its rendering, and the result is sent back to the client.

You can observe the effect that introducing asynchronous pages into your application has by performing load tests on your system and observing the average response times of individual pages. Slow synchronous pages that are I/O-bound often occupy threads in the thread pool for long slices of time, preventing other requests that normally are very responsive from responding quickly. Relegating the slow I/O-bound pages to be serviced on alternative threads should ensure that all of your "fast" pages always return promptly.

Marking your pages as asynchronous and using asynchronous tasks is not the only way to change the way the thread pool behaves. You can also modify the settings of the thread pool through the machine.config file's processModel element (note that you cannot apply this configuration setting in local web.config files). By default, the thread pool defaults to a cap of 100 worker threads per CPU and 100 I/O threads per CPU (used to service asynchronous I/O calls when they complete). It is unlikely you would need to adjust these values, since 100 worker threads should be more than sufficient for any server. What you may want to consider adjusting, however, is the minimum number of worker threads. This tells the thread pool to allocate a fixed number of threads and to keep them running ready to process requests. In practice, the thread pool's heuristics for allocating new threads does not seem to kick in very quickly, so any time you receive a burst of a large number of concurrent requests, you often see requests queued even though the thread pool has not reached its maximum count. Listing 9-7 shows an example of setting the minimum thread-pool count for worker threads to 50 to alleviate the implicit constraints of the thread pool when responding to a burst of requests.

Listing 9-7. Changing the minimum worker thread count in machine.config

<configuration>   <system.web>     <processModel minWorkerThreads="50" />     <!-- ... -->    </system.web> </configuration> 

To make the best decisions about asynchronous pages and thread-pool settings, it is always best to devise load tests for your applications that model their anticipated deployed use.

AddOnPreRenderCompleteAsync

Technically, it is also possible to build asynchronous processes directly into your asynchronous pages by calling the AddOnPreRenderCompleteAsync method with two event handlersbegin and end. The only constraint is that your begin event handler must return an IAsyncResult interface, adhering to the Asynchronous Programming Model found throughout .NET. If your begin event handler returns a valid IAsyncResult interface, it will register with the callback delegate exposed by the interface, and return the primary thread back to the thread pool to service subsequent requests. Once your asynchronous operation completes and signals the callback method, ASP.NET will resume execution of the Page class at the PreRenderComplete method, on whatever thread your asynchronous work called it back on. Listing 9-8 shows a sample asynchronous page implementation that uses this technique to map the begin and end event handlers onto an asynchronous Web service request.

Listing 9-8. Using AddOnPreRenderCompleteAsync

<%@ Page Language="C#" Async="true" %> <script runat="server">    Slow slowWebService = new Slow();   protected void Page_Load(object sender, EventArgs e)   {     BeginEventHandler bh = new BeginEventHandler(this.BeginGetAsyncData);     EndEventHandler eh = new EndEventHandler(this.EndGetAsyncData);     AddOnPreRenderCompleteAsync(bh, eh);   }   //... </script> IAsyncResult BeginGetAsyncData(Object src, EventArgs args,                                 AsyncCallback cb, Object state) {   // Note-this is serviced on the same thread as Page_Load   // but a different thread is used to service EndGetAsyncData   //   return slowWebservice.BeginHelloWorld(cb, state); } void EndGetAsyncData(IAsyncResult ar) {   string ret = slowWebservice.EndHelloWorld(ar);   // Use ret here } 

Thread-Relative Resources

Each of the asynchronous mechanisms we have looked at in this chapter give you the ability to execute code on a different thread from the one that is initially allocated to process your page. One of the consequences of this is that thread-relative resources may not be propagated to these secondary threads, which may impact what you do in code that runs on these secondary threads. The primary thread-relative resources that concern ASP.NET developers include the culture, UI culture, HttpContext, and Windows identity. Fortunately, both the asynchronous task feature and the AsyncOperationManager are implemented to propagate everything, including the Windows identity, to all secondary threads. This means that you can use HttpContext.Current (which retrieves the current context from the thread) and that you can be sure that the culture settings for your thread are the same as they were for the initial request thread. This is not true for the last technique we presented, using AddOnPreRenderCompleteAsync, which is yet another reason to prefer asynchronous tasks for this technique.




Essential ASP. NET 2.0
Essential ASP.NET 2.0
ISBN: 0321237706
EAN: 2147483647
Year: 2006
Pages: 104

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net