Invoking Asynchronous Tasks in A
Invoking Asynchronous Tasks in ASP.NET 2.0
Introduction
Invoking multiple tasks that are slow or lengthy always poses a challenge in
front of developers. Normal approach is to develop a multithreaded component and
delegate the tasks to it. In ASP.NET 1.x there was no direct support at web form
level for executing such tasks in asynchronous manner. ASP.NET 2.0 on the other
hand allows you to execute tasks asynchronously from within the web form itself.
This article explores the two possible approaches with examples.
If you are not familiar with asynchronous operations then I suggest that you
read
Asynchronous Programming In .NET and
Invoking
Methods Asynchronously using Delegates for better understanding.
Possible approaches
There are two approaches that you can take for developing asynchronous pages:
- Using RegisterAsyncTask method of Page class
- Using AddOnPreRenderCompleteAsync method of Page class
Understanding the asynchronous task execution model
Before we delve further into the topic let's first understand how it works.
Under normal condition ASP.NET executes all the code in sequence. That means
all the events such as Init, Load, PreRender and Unload are executed one after
the other as follows:
- PreInit
- Init
- InitComplete
- PreLoad
- Load
- LoadComplete
- PreRender
- PreRenderComplete
- Unload
When you enable asynchronous processing using any of the methods listed
above, the sequence of operation changes as follows:
- PreInit
- Init
- InitComplete
- PreLoad
- Load
- LoadComplete
- PreRender
- Your asynchronous tasks are started
- Your asynchronous tasks complete
- PreRenderComplete
- Unload
As you can see ASP.NET plugs in your asynchronous operations between
PreRender and PreRenderComplete events. Note that ASP.NET blocks the rendering
of the page till all the asynchronous tasks that you started are not complete.
When you run a web form having asynchronous operations, ASP.NET does the
following:
- ASP.NET picks up a thread from thread pool to execute your page
- Till PreRender event that thread does the work and then it is returned
to the thread pool
- The aysnchronous operation is started
- When the asynchronous operation completes, ASP.NET picks up another
thread from the thread pool and executes remaining events on that thread
The Async attribute
Whatever approach you take for implementing the asynchronous tasks you need
to set Async attribute of @Page directive to true.
<%@ Page Language="C#" Async="true" %>
NOTE: Strictly speaking it is not mandatory for RegisterAsyncTask that the
page must have Async set to true. But using this attribute tells ASP.NET to use implementation of IHttpAsyncHandler
internally.
BeginEventHandler and EndEventHandler delegates
When ASP.NET is about to start your asynchronous operation it expects a
function to start your operation. The signature of this function must match the
signature of BeginEventHandler delegate. The following code shows the signature
of this delegate.
IAsyncResult BeginAsyncOperation
(object sender, EventArgs e,
AsyncCallback cb, object state)
The AsyncCallBack parameter supplies the delegate to call when the
asynchronous method call is complete. The state parameter can be used to pass
some application specific state information. The return type of this function is
IAsyncResult. Note that asynchronous pattern of .NET framework (BeginXXXX)
always returns an object implementing this interface. This holds true for
asynchronous web service calls, network programming tasks and file IO.
Just like BeginEventHandler there is another delegate called EndEventHandler.
When the asynchronous task completes ASP.NET invokes a function that you supply.
The function must match the ssignature of EndEventHandler delegate. The
following code shows the signature:
void EndAsyncOperation(IAsyncResult ar)
This method receives an instance of IAsyncResult that you returned during
BeginEventHandler.
These delegates are required in both of the asynchronous invocation
approaches.
Using RegisterAsyncTask method
In order to illustrate the use of RegisterAsyncTask method we will develop
two web services called WebService1.asmx and WebService2.asmx with a web method
called HelloWorld. The HelloWorld web method looks as shown below:
[WebMethod]
public string HelloWorld(string name)
{
System.Threading.Thread.Sleep(3000);
return "Hello " + name;
}
The web method takes a string parameter called name and returns a string by
concatenating it to "Hello". Just to simulate a lengthy or slow operation we
have introduced a delay by putting the thread to sleep for 3 seconds.
We will be calling this web method asynchronously from a web form.
In the code behind of the web form we need to create functions matching the
signatures of BeginEventHandler and EndEventHandler delegates respectively. The
following code shows these functions.
private IAsyncResult BeginAsyncOperation1
(object sender, EventArgs e,AsyncCallback cb, object state)
{
StreamWriter writer=
File.AppendText(Server.MapPath("log1.txt"));
writer.WriteLine(DateTime.Now);
writer.Close();
WSProxy1.WebService1 obj1 =
(WSProxy1.WebService1)state;
obj1 = new WSProxy1.WebService1();
return obj1.BeginHelloWorld("Tom",cb, state);
}
private void EndAsyncOperation1(IAsyncResult ar)
{
StreamWriter writer =
File.AppendText(Server.MapPath("log1.txt"));
writer.WriteLine(DateTime.Now);
writer.Close();
WSProxy1.WebService1 obj1 =
(WSProxy1.WebService1)ar.AsyncState;
Label1.Text = Label1.Text + "<br>" +
obj1.EndHelloWorld(ar);
}
private void TimeoutAsyncOperation1(IAsyncResult ar)
{
//do nothing
}
private IAsyncResult BeginAsyncOperation2
(object sender, EventArgs e,
AsyncCallback cb, object state)
{
StreamWriter writer=
File.AppendText(Server.MapPath("log2.txt"));
writer.WriteLine(DateTime.Now);
writer.Close();
WSProxy2.WebService2 obj2 =
(WSProxy2.WebService2)state;
obj2 = new WSProxy2.WebService2();
return obj2.BeginHelloWorld("Jerry",cb, state);
}
private void EndAsyncOperation2(IAsyncResult ar)
{
StreamWriter writer =
File.AppendText(Server.MapPath("log2.txt"));
writer.WriteLine(DateTime.Now);
writer.Close();
WSProxy2.WebService2 obj2 =
(WSProxy2.WebService2)ar.AsyncState;
Label1.Text = Label1.Text + "<br>" +
obj2.EndHelloWorld(ar);
}
private void TimeoutAsyncOperation2(IAsyncResult ar)
{
//do nothing
}
In the BeginXXXX functions we create an instance of web service proxies and
start the asynchronous operation by calling BeginHelloWorld methods. The return
value from BeginHelloWorld is returned from the BeginAsyncOperation1 and
BeginAsyncOperation2 functions respectively. We also log the time stamp in a
text file which will prove that the operations are indeed happening
asynchronously.
In the EndXXXX functions we retrieve the instance of Proxy on which we called
the BeginHelloWorld method and then call EndHelloWorld method on that proxy
instance. As before we log time stamp in a log file.
The TimeoutXXXX function gets called in case the asynchronous task exceeds
the timeout value. By default the timeout value is 45 seconds. You can configure
it using the AsyncTimeout attribute of @Page directive. In our example we do not
have anything specific in these functions.
<%@Page Language="C#" AsyncTimeout="30" %>
Now, let's see how to actually trigger these functions.
protected void Button1_Click(object sender, EventArgs e)
{
WSProxy1.WebService1 obj1=new WSProxy1.WebService1();
WSProxy2.WebService2 obj2=new WSProxy2.WebService2();
BeginEventHandler beginHandler1 =
new BeginEventHandler(BeginAsyncOperation1);
EndEventHandler endHandler1 =
new EndEventHandler(EndAsyncOperation1);
EndEventHandler timeoutHandler1 =
new EndEventHandler(TimeoutAsyncOperation1);
object state1 = obj1;
PageAsyncTask task1 =
new PageAsyncTask(beginHandler1, endHandler1,
timeoutHandler1, state1,true);
BeginEventHandler beginHandler2 =
new BeginEventHandler(BeginAsyncOperation2);
EndEventHandler endHandler2 =
new EndEventHandler(EndAsyncOperation2);
EndEventHandler timeoutHandler2 =
new EndEventHandler(TimeoutAsyncOperation2);
object state2 = obj2;
PageAsyncTask task2 =
new PageAsyncTask(beginHandler2, endHandler2,
timeoutHandler2, state2,true);
Page.RegisterAsyncTask(task1);
Page.RegisterAsyncTask(task2);
}
Here, we simply create instances of BeginEventHandler and EndEventHandler
delegates and point them to our functions. We also create instances of web
service proxies which are used in these functions.
The RegisterAsyncTask method of page class takes a parameter of type
PageAsyncTask. While creating the instance of PageAsyncTask class you need to
pass a delegates pointing to begin, end and timeout functions. Additionally you
can pass application specific state (instances of web service proxies in our
example). The last boolean parameter indicates whether the tasks are to be
executed in parallel (simultaneously) or no. Since our tasks are independent of
one another we set this parameter to true.
Calling RegisterAsyncTask method schedules the tasks on the thread pool. You
can call this method several times to schedule multiple tasks.
Using AddOnPreRenderCompleteAsync method
In this approach the delegates and functions used are the same as we used
above. However, you need to call AddOnPreRenderCompleteAsync method of the page
class passing begin and end handlers. This method does not allow for timeout
handling. You can call this method multiple times to invoke multiple
asynchronous operations. Note that AddOnPreRenderCompleteAsync does not not
allow you to call multiple tasks in parallel.
Here is a sample call to this method:
WSProxy1.WebService1 obj1 =
new WSProxy1.WebService1();
BeginEventHandler beginHandler1 =
new BeginEventHandler(BeginAsyncOperation1);
EndEventHandler endHandler1 =
new EndEventHandler(EndAsyncOperation1);
object state1 = obj1;
Page.AddOnPreRenderCompleteAsync
(beginHandler1, endHandler1, state1);
Code Download
The complete source code of the above examples is available for download
along with this download. Just click on the Download link on the top.
Summary
ASP.NET 2.0 makes asynchronous programming easy by providing two
approaches. In general if you want to execute multiple asynchronous tasks in
parallel or otherwise then RegisterAsyncTask is neat and recommended where as if you want to execute just a single
asynchronous task then AddOnPreRenderCompleteAsync method can be used.