Home
    Shop
    Advertise
    Write For Us
    Affiliate
    Newsletter
    Contact

Use Asynchronous Execution Pattern in ASP.NET

What is that?

Asynchronous operations or execution pattern is another face of multithreading.

 

Some operations are time consuming, take long time to complete, and block user interface waiting for their completion. These operations fall into two main categories: I/O bound operations, and CPU bound operations. To solve this problem we mainly make use of a new thread to execute the long task operation, while the main application thread continues its normal execution without blocking.

Because this scenario is so common, .NET framework 2005 provides you with the asynchronous operation execution pattern. In this pattern you choose a method to execute asynchronously, means that not blocking your main application execution path waiting for this method to complete (synchronous operation). Asynchronous operations will be executed independently, in an automatic way, which saves you from dealing directly with threads.

In the past, and before ASP.NET 2.0 technology, the task of creating asynchronous pages was so hard. Now with ASP.NET 2.0 new features, you can simply do that.

In this tutorial, together, we will explore the world of asynchronous execution pattern, how to use it, how to manage it, and giving a true example code about it.

Using Asynchronous Execution Pattern

.NET framework gives you the ability to call any function asynchronously, or synchronously. To give this ability to a function, it must has the same signature as a predefined delegate. The function will be called through this delegate. If you want it to operate synchronously, you simply can call it directly by its name, or call it using the predefined delegate "Invoke()" method. If you want to call it asynchronously, you have to call it through the predefined delegate "BeginInvoke()" method. To be more clear, let us build our example:

Open Visual Studio 2005, and choose "File / New / Web Site" from the main menu. Switch to the "Default.aspx.vb" code page, and write the following delegate declaration statement.

    4     Public Delegate Sub LongTimeTask_Delegate(ByVal s As String)

This is the delegate by which you will perform asynchronous execution of your method. Your long time task method must has the same signature as this delegate's signature. The compiler automatically creates "Invoke", "BeginInvok", and "EndInvok" methods for the delegate.

Now, create your long time task method as shown in the following code.

   12     Public Sub LongTimeTask(ByVal s As String)
   13         ' Write your time consuming task here
   14     End Sub

Calling this method asynchronously means that: The .NET framework will automatically assign a new thread from the thread pool to this method to execute it independently and away from the main application execution path. The management of this new thread will be held automatically behind the scenes and without bothering you.

Initiating asynchronous execution

After creating a delegate that matches the signature of the method you want to call asynchronously, create an instance of this delegate containing a reference to the method you want to execute, then execute the method asynchronously by calling the delegate's instant "BeginInvok" method.

The above is all what you need to execute a method asynchronously. Let us examine this through our example code.

After declaring our delegate as shown above in code line 4, write the following lines:

    7         Dim d As LongTimeTask_Delegate
    8         d = New LongTimeTask_Delegate(AddressOf LongTimeTask)
    9 
   10         Dim R As IAsyncResult
   11         R = d.BeginInvoke("abcdef", Nothing, Nothing)

As you can see, first we define a variable of type delegate "d", then we assign this value a new instance of the delegate we defined early with a reference to the method we want to execute asynchronously, which in our case is the "LongTimeTask" method. Then we define another variable "R" of type "IAsyncResult" Interface, which is used to represent the status of an asynchronous operation. In line #11 above, we call the "BeginInvok" method of the delegate instance "d", and assign its result to the "R" variable.

There are some notes about the "BeginInvok" method used above:

  • This method has the same signature as the delegate we created in line #4, plus two more parameters.
  • These two more parameters are used to support asynchronous completion as we will see in the next lines....
  • This method returns an object of type "IAsyncResult", which you can use to monitor the status of your asynchronous operation, obtain the result of your operation after completion, or block the current running thread until your asynchronous operation completes. We will examine all of that later in this tutorial.

Callback and AsyncState

"Callback" and "AsyncState" are the additional two parameters at the end of the "BeginInvok" method we mentioned above. Those two parameters are used as follows:

Callback parameter is a delegate of type "System.AsyncCallback" delegate, which references the callback method to be called automatically when the asynchronous operation is completed. This means that, if this parameter is not null (nothing in Visual Basic) then the runtime will call the method you specified when the asynchronous method completes. You can specify a null or nothing reference to this parameter to indicate that, you don't want to execute any callback methods automatically after completion, and that you will do it manually if you need to.

AsyncState parameter is of type "object". You can assign any object to this parameter. The asynchronous method does not use this object, instead it makes it available to you after completion. You can use this object for example to associate useful state information to your method, or as an identification object to your method, helping you manage more than one asynchronous method having the same callback method.

To give an example about how to use the "Callback" parameter, we will change line #11 above by this line:

   11         R = d.BeginInvoke("abcdef", _
   12          New AsyncCallback(AddressOf TaskCompleted), _
   13          Nothing)

As you can see, the second parameter to the "BeginInvok" method is changed from "Nothing" to a new instant of the "AsyncCallback" delegate which reference the "TaskCompleted" method as a callback method. Now create the "TaskCompleted" method as follows:

   19     Public Sub TaskCompleted(ByVal R As IAsyncResult)
   20         ' Write here code to handle the completion of 
   21         ' your asynchronous method
   22     End Sub

Note that you must specify an input parameter of type "IAsyncResult" to this method, or otherwise an error will be reported. By this, when the runtime finishes executing the "LongTimeTask" method asynchronously, the "TaskCompleted" method will then be executed automatically.

Managing Asynchronous Completion

In most cases, you will want to know when your asynchronous operation is completed so that you can obtain the result of this operation, start another operation depending on the completion of this one, or to take some other actions. The asynchronous execution pattern provides more than one technique that you can use to determine whether an asynchronous operation has completed or not.

These techniques are:

Callbacks

This is the most flexible technique you can use, and it needs no monitoring from your side. When your asynchronous operation finishes execution, the runtime automatically acquire a new thread from the thread pool and executing your call back method in its context, and passing the "IAsyncResult" that identifies the completed operation to your callback method. This is the technique we have described in the above section.

Blocking

This technique is rarely used because of its few advantages over synchronous operations. It is never used for parallelism as we will see in the next example:

To use blocking, you have to call the "EndInvok" method of the asynchronous delegate instance, and pass the "IAsyncResult" object instance to it. When you do this, the calling method will be blocked until the called asynchronous method completes. As you see this is against parallelism, but it may be useful in some cases.

To test this, see the following lines of code, which call our method asynchronously, then block until it completes. Give attention to line #16 below:

    7         Dim d As LongTimeTask_Delegate
    8         d = New LongTimeTask_Delegate(AddressOf LongTimeTask)
    9 
   10         Dim R As IAsyncResult
   11         R = d.BeginInvoke("abcdef", _
   12          Nothing, _
   13          Nothing)
   14 
   15         'Block until the asynchronous operation is complete
   16         d.EndInvoke(R)

Polling

Using polling is a very bad idea, it is inefficient, and put limitations on what you can do while polling. To use polling, you have to write a loop that tests the completion state of the asynchronous operation, by making use of the "IsCompleted" property of the "IAsyncResult" object. See the following code:

    7         Dim d As LongTimeTask_Delegate
    8         d = New LongTimeTask_Delegate(AddressOf LongTimeTask)
    9 
   10         Dim R As IAsyncResult
   11         R = d.BeginInvoke("abcdef", Nothing, Nothing)
   12 
   13         ' Useing of polling approach
   14         While (Not R.IsCompleted)
   15             ' Do whatever you want waiting for the 
   16             ' asynchronous method to compelet.
   17         End While

As you can see, polling is just a waste of time and CPU resources. The only task it may be suitable for, is to be used to display a process indicator on smart client applications during short asynchronous operations.

Waiting

The waiting approach is similar to the blocking approach above, but instead you can specify a timeout for the waiting operation, and after that timeout you can check whether the asynchronous operation is complete or not. This waiting itself does not utilize much CPU resources and this is the advantage of this technique over pooling.

You can also use waiting like polling by putting the wait statement in a loop testing for the asynchronous method completion. Unlike the polling approach, the wait statement is much more efficient than the polling described above. Waiting statement puts the CPU in an efficient sleep, instead of code level loop.

To use the wait statement, you have first to declare a variable of type "WaitHandle", then assign the "AsyncWaitHandle" method of the "IAsyncResult" instance to the declared "WaitHandle", then call the "WaitOne" method as shown in the following code:

    7         Dim d As LongTimeTask_Delegate
    8         d = New LongTimeTask_Delegate(AddressOf LongTimeTask)
    9 
   10         Dim R As IAsyncResult
   11         R = d.BeginInvoke("abcdef", Nothing, Nothing)
   12 
   13         ' Using the waiting approach
   14         Dim WH As Threading.WaitHandle
   15         WH = R.AsyncWaitHandle
   16         WH.WaitOne(3000, False)

The above code will stop the caller thread for 30 seconds waiting for the called thread to complete its execution. You can use the "IsCompleted" property of the "IAsyncResult" instance to examine for the method completion.

To download the complete code used in this tutorial, just click here.


Tutorial toolbar:  Tell A Friend  |  Add to favorites  |  Feedback  |