Saturday, December 12, 2009

Multi-threaded Asynchronous Programming

It's been a while since I visited this subject and I felt I needed to cover a very important missing piece - Async web pages which are primarily used as a server performance enhancement. There are two main methods we will be looking at that are used to execute operations asynchronously from web pages:

Page.AddOnPreRenderCompleteAsync() and Page.AddOnPreRenderCompleteAsync().

Part I. Setup.

There is not a lot of setup to do to prepare our pages for asynchronous behavior. In the page directive we just need to specify: Async="true". Below is the page directive for one of my sample pages.

<%@ Page Async="true" Language="C#" AutoEventWireup="false" CodeFile="RegisterAsyncTask.aspx.cs" Inherits="RegisterAsyncTaskPage" %>

We can also set the timeout here for long running transactions. The default is 45 seconds. There are probably very few situations where we would want longer running pages because the user experience would be very poor, but I could see shortening the timeout up a bit using the following declaration in the page directive: AsyncTimeout="20".

Just FYI: I have created a "fake" long running process for this sample as a placeholder for the actual long-running process that would exist in a real project such as a web service or database call.


Use the AddOnPreRenderCompleteAsync method to add handlers to an asynchronous Web page.
You can register multiple asynchronous handlers; however, only one handler runs at a time. If you want to process multiple asynchronous methods simultaneously, you should use a single BeginEventHandler method and launch multiple asynchronous operations from that handler.
The asynchronous handlers are called between the PreRender and PreRenderComplete events.
First, all Page events (through the PreRender event) are run, and then each registered BeginEventHandler method is called. When the handler completes, the corresponding EndEventHandler method is called. If there are multiple asynchronous handlers, the next handler is called.
After the registered asynchronous event handlers have been called, the rest of the page events are called, beginning with the PreRenderComplete event.


The following code example uses an asynchronous request to display the HTML source code of the local Web server's default page in a TextBox control.



Visual Basic

%@ page language="VB" Async="true"%>
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
script runat="server">
  Dim myRequest As System.Net.WebRequest

  Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
    Label1.Text = "Page_Load: Thread #" & System.Threading.Thread.CurrentThread.GetHashCode()

    Dim bh As New BeginEventHandler(AddressOf Me.BeginGetAsyncData)
    Dim eh As New EndEventHandler(AddressOf Me.EndGetAsyncData)

    Me.AddOnPreRenderCompleteAsync(bh, eh)

    ' Initialize the WebRequest object.
    Dim address As String
    address = "http://localhost/"
    myRequest = System.Net.WebRequest.Create(address)

  End Sub

  Function BeginGetAsyncData(ByVal src As Object, ByVal args As EventArgs, ByVal cb As AsyncCallback, ByVal state As Object) As IAsyncResult
    Label2.Text = "BeginGetAsyncData: Thread #" & System.Threading.Thread.CurrentThread.GetHashCode()
    Return Me.myRequest.BeginGetResponse(cb, state)
  End Function

  Sub EndGetAsyncData(ByVal ar As IAsyncResult)
    Label3.Text = "EndGetAsyncData: Thread #" & System.Threading.Thread.CurrentThread.GetHashCode()

    Dim myResponse As System.Net.WebResponse
    myResponse = Me.myRequest.EndGetResponse(ar)

    Dim reader As New System.IO.StreamReader(myResponse.GetResponseStream())
    result.Text = reader.ReadToEnd()
    myResponse.Close()
  End Sub

/script>
html  >
  head runat="server">
    title>
      Page.AddOnPreRenderCompleteAsync Example/title>
  /head>
  body>
    form id="form1" runat="server">
      asp:label id="Label1" runat="server">
        Label 1/asp:label>br />
      asp:label id="Label2" runat="server">
        Label 2/asp:label>br />
      asp:label id="Label3" runat="server">
        Label 3/asp:label>br />
      asp:textbox id="result" runat="server" textMode="multiLine" ReadOnly="true" columns="80" rows="25" />
    /form>
  /body>
/html>