All of the web service client examples we've used so far make a call to the web service and wait for data before continuing work. While this approach might be appropriate for small, quick calls, such synchronous calls might yield an unacceptable response time. The web service has to perform a time-consuming task. Asynchronous web service calls are the answer in this case. This section explores how the .NET Framework web services enable clients to call web methods asynchronously, yielding a better overall response time for an application.
If you look back at the generated source for the web service proxy in the previous section, you will find that for every web method there are three separate proxy methods generated. The following shows the three proxy methods: GetAuthors( ), BeginGetAuthors( ), and EndGetAuthors( ):
public System.Data.DataSet GetAuthors( ) { object[] results = this.Invoke("GetAuthors", new object[0]); return ((System.Data.DataSet)(results[0])); } public System.IAsyncResult BeginGetAuthors(System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("GetAuthors",new object[0],callback,asyncState); } public System.Data.DataSet EndGetAuthors(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((System.Data.DataSet)(results[0])); }
In the earlier web service consumer example, we've used the proxy method GetAuthors( ) to call the web method on the other side of the HTTP channel. The following example shows how the other two methods can be used to perform an asynchronous web method call.
As it turns out, there are a couple of different ways to use the BeginMethodName( ) and EndMethodName( ) to call the web method asynchronously. We will show you each one and describe how they are different from one another.
The first way will be calling BeginMethodName( ) passing in null for both the callback and the asyncState objects. We will poll on the IAsyncResult object until it is completed while simulating other works. When the call is done, we call EndMethodName( ) to complete the web method call and obtain the DataSet result:[17]
[17] The proxy in this example was generated with a namespace "WSPubsWS" instead of the default global namespace.
WSPubsWS.PubsWS oProxy = new WSPubsWS.PubsWS( ); IAsyncResult result; // Async call polling (cannot run this in debugger) Console.Write("Async Polling"); result = oProxy.BeginGetAuthors(null, null); while(!result.IsCompleted) { Console.Write(" . . . "); Thread.Sleep(5); } DataSet oDSAsync = oProxy.EndGetAuthors(result); oDSAsync.WriteXml("outputpolling.xml"); Console.WriteLine("done");
Although this might not be the best way, it demonstrates one way of performing an asynchronous call to a web method.
The second way still does not use the callback mechanism. Be patient. Again, we use the IAsyncResult object to perform a block waiting on the remote call. Block waiting put the current thread to sleep, giving up the CPU to other processes or threads until the wait handle is signaled. Depending on your application, this might be how you would want to do it:
// Async call with waithandle Console.WriteLine("Async, processing then wait for handle"); result = oProxy.BeginGetAuthors(null, null); Console.WriteLine(" . . . Processing some more . . . "); result.AsyncWaitHandle.WaitOne( ); // block on handle DataSet oDSAsyncWait = oProxy.EndGetAuthors(result); oDSAsyncWait.WriteXml("outputWaiting.xml"); Console.WriteLine("done");
The third and fourth ways use the callback mechanism so we will know exactly when to complete the web method call with the EndMethodName( ). We make the call to the BeginMethodName( ) passing in the callback delegate. When the web method call completes, we will get called back. Since this example is done as a console application, a little more setting up is required; otherwise, the application might exit before the callback occurs. This is done through the AutoResetEvent object (a synchronization primitive similar to a mutex). Basically, we want to set up a wait object, call the async web method, and wait for the signal from the callback before continuing with the application:
// Async call with callback 1 AutoResetEvent oWait = new AutoResetEvent(false); Console.WriteLine("Async, processing then wait for callback"); CallBack cb = new CallBack(oProxy, oWait); Result =oProxy.BeginGetAuthors(new AsyncCallback(cb.CallBackMethod), null); Console.WriteLine(" . . . Processing some more . . . "); Console.WriteLine("Application waits for callback rendezvous"); oWait.WaitOne( ); Console.WriteLine("done");
The CallBack class needs to have the proxy to complete the web method call. It also needs the AutoResetEvent to signal the main application flow to continue. The following is the definition of the CallBack class at this moment:
public class CallBack { public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) { m_oProxy = oProxy; m_oWait = oWait; } public void CallBackMethod(IAsyncResult result) { DataSet oDSAsyncCB; oDSAsyncCB = m_oProxy.EndGetAuthors(result); oDSAsyncCB.WriteXml("outputCallback.xml"); m_oWait.Set( ); } private WSPubsWS.PubsWS m_oProxy; private AutoResetEvent m_oWait; }
In the next example, we pass both the callback and the asyncState object to the BeginMethodName( ) method. We already know all about the callback object. What about this asyncState thing? It actually can be anything you want to pass to the callback object. We will pass the proxy object itself via this asyncState object so we will know how to complete the web method call when the callback is invoked. The following code segment shows how this is done:
// async call with callback 2 oWait.Reset( ); Console.WriteLine("Async, processing then wait for callback also"); CallBack cbWithData = new CallBack(oWait); result=oProxy.BeginGetAuthors(new AsyncCallback(cbWithData.CallBackMethod), oProxy); Console.WriteLine("Processing some more data"); Console.WriteLine("Application waits for callback rendezvous"); oWait.WaitOne( ); Console.WriteLine("done");
This time, the CallBack object is instantiated with only one parameter. Changes to the CallBack class are highlighted in the following code block. When the callback method is invoked, you can cast the IAsyncResult object's AsyncState back to whatever type you passed into the second parameter of the BeginMethodName( ) method. In this case, we cast the AsyncState object back to the proxy in order to complete the web method call with EndMethodName( ) to obtain the resulting DataSet:
public class CallBack { public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) { m_oProxy = oProxy; m_oWait = oWait; } public CallBack(AutoResetEvent oWait) { m_oWait = oWait; } public void CallBackMethod(IAsyncResult result) { DataSet oDSAsyncCB; if(m_oProxy == null) { WSPubsWS.PubsWS oTmp = (WSPubsWS.PubsWS)result.AsyncState; oDSAsyncCB = oTmp.EndGetAuthors(result); oDSAsyncCB.WriteXml("outputCallbackWithData.xml"); } else { oDSAsyncCB = m_oProxy.EndGetAuthors(result); oDSAsyncCB.WriteXml("outputCallback.xml"); } m_oWait.Set( ); } private WSPubsWS.PubsWS m_oProxy; private AutoResetEvent m_oWait; }
In a Windows Form application, you can start the web service call via a button clicked or a menu clicked and still can have other parts of the application be responsive to the user. We will leave that example to you as an exercise.