"Watching non-programmers trying to run software companies is like watching someone who doesn't know how to surf trying to surf." -- Joel Spolsky
We've been getting a few of this genre of questions recently on our forums, and I decided it would be a good time to put something together. And since we seem to be getting a lot of VB.NET'ers, I've broken with tradition and will post code in both VB.NET and C#, as well as in the downloadable solution.
First let's ask and answer a few basic questions:
Q: What does "Asynchronous" really mean?
A: Literally, "not synchronous"; not at the same time. Asynchronous is a programming term that means a type of two-way communication that occurs with a time delay, allowing participants to respond at their own convenience. In other words, we can make a method call and go about our work without delay, and when the results are ready, we are notifed appropriately and are able to act upon them.
Q: When would I want to use asynchronous web service methods?
A: Whenever you want! Particularly, if the method call may take some time and you do not want to have your user interface "frozen" while waiting for the results. Asynchronous WebMethod calls are as efficient, and sometimes more efficient than their synchronous siblings.
In ASP.NET, starting with ASP.NET 1.0, asynchronous versions of WebMethods are wired into the WebService infrastructure automatically. On the server side, you need do nothing except create your WebMethod, and the WSDL contract that .NET uses to generate a proxy class to make calls on service methods will automatically include asynchronous versions of each method. You can identify these because they begin with "Begin"<WebMethodName> and "End"<WebMethodName>.
Q: How does all this work?
A: Asynchronous methods in webservices work the same way as asynchronicity in any other API - the inbound method call is intercepted and acted upon. However, the results are sent via what is referred to as a "Callback". A callback is a method on the client which is "rung up" or called back, with the results of the call. What this means is that a method call to a BeginXXX WebMethod returns immediately, it is said to be "non blocking". This allows your UI to remain free so the user continues to have full use of its features while waiting for the result. When the result arrives, it is the separate callback method that receives and processes it.
Now let's take a look at some simple ASP.NET 1.1 VB.NET code for a WebMethod that accepts a string, and packages it up, returning a DataSet object as the return type:
Public Function GetDataSet(ByVal value As String) As DataSet
Dim ds As New DataSet
Dim dt As New DataTable
Dim row As DataRow
row = dt.NewRow
row("Test") = value
The above is quite simple and should require no explanation. However, if we take a look at the proxy code that is generated by creating a WebReference to this service, we see:
Those two artful red arrows show the Asynchronous version set of methods that ASP.NET automatically generated for us to match our synchronous "GetDataSet" method.
Consuming Asynchronous Methods
Now comes the fun part - how to use these new guys!
As with any WebService method call, we need an instance of our proxy class. But that's where things change. Consider the following sample VB.NET code from a Windows Forms application that consumes our "GetDataSet" method:
' This is our button click handler. It creates the service proxy instance, and calls the BEGINXXX method:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim ws As New peter.Service1
Dim ar As IAsyncResult = ws.BeginGetDataSet("Testing 1234", New AsyncCallback(AddressOf WsCallback), ws)
' This is our Callback method. It RECEIVES the callback in the form of an IAsyncResult object we can act on.
Private Sub WsCallback(ByVal ar As IAsyncResult)
Dim cb As peter.Service1 = ar.AsyncState
Dim ds As DataSet = DirectCast(cb.EndGetDataSet(ar), DataSet)
Me.Label1.Text = ds.Tables(0).Rows(0)("Test")
Note that the "BeginGetDataSet" method call has a distinctive signature - the string input parameter is passed first (there could be more than one parameter) , then we create a new AsyncCallback object that "points" to the WsCallback callback (the "receiver") method. This Button1_Click method returns immediately - there is no blocking of the thread. Note also that in this case I decided to pass the actual "ws" proxy class instance itself as the last parameter, which is the AsyncState parameter, of type Object.
In the WsCallback method, we receive the IAsyncResult and I immediately get back my original proxy class instance by casting it out of the IAsyncResult object's AsyncState property. Then, all I need to do is call the asynchronous "EndGetDataSet" method, passing in the AsyncResult object, and cast this to the return type of DataSet.
At this point I have a valid DataSet object and I can do whatever I "am supposed to do" with it - update a DataGrid, whatever. In this simplified example I simply get out my one column value from my single DataTable row and update the Label with it.
This infrastructure has a number of variations, including the use of delegates. Also note that the return value of the original ws.BeginGetDataSet method call is also an IAsyncResult object, and this can be used as well.
While there are a number of variations on this theme, the above pattern and signatures are useful in the vast majority of instances.
Also, I think it is important to bear in mind that in ASP.NET 2.0, we have some interesting enhancements, most notably that there is now an Event-driven WebService infrastructure, which makes for a much cleaner use pattern on the client / consumer side. In addition, it provides the ability to work with servers that enable HTTP (Gzip) compression to conserve bandwidth.
The C# Consumer pattern looks like this:
private void button1_Click(object sender, System.EventArgs e)
peter.Service1 ws = new CSharpTester.peter.Service1();
ws.BeginGetDataSet("Testing 1234",new AsyncCallback(WsCallback),ws);
private void WsCallback( IAsyncResult ar)
peter.Service1 ws = (peter.Service1)ar.AsyncState;
DataSet ds = ws.EndGetDataSet(ar);
The downloadable Visual Studio.NET 2003 solution below has a VB.NET Service page, and both C# and VB.NET versions of a Winforms consumer application.
Download the Visual Studio.NET 2003 Solution that accompanies this article