To demonstrate calling a Web Service asynchronously, we will begin by creating a Web Service that will be consumed in all of the examples that follow.
Listing 8.1 demonstrates a Web Service with a WebMethod that will take some time to execute. Create a new Web Service Project called VirtualFlights. Add Listing 8.1 to the service.
Listing 8.1 includes a typical WebMethod that would receive 6 parameters. Four parameters are passed into the WebMethod by value and will be used to perform some lengthy search. Two additional parameters are passed by reference. These parameters will be modified by the WebMethod to return the results of the execution. To simulate the lengthy operation, the Thread will pause for 5 seconds and then set the parameters, passed in by reference to the result values, which in this case are dummy results.
Now that we have a Web Service, let's create a client that will make use of a callback method to retrieve the results from the Web Service. The client will include implementations of both synchronous and asynchronous calls.
1. |
Add a new Visual Basic Windows Application Project to the solution. Rename the project FlightTest. | ||||||||||||||||||||||||||||||||||||
2. |
Because this project needs to consume the Web Service, add a Web Reference to the VirtualFlights Web Service you previously created. | ||||||||||||||||||||||||||||||||||||
3. |
Now it's time to prepare the Windows Form. Add the controls listed in Table 8.2 to the form and set their properties as listed. The resulting form should look similar to Figure 8.1. Figure 8.1. The consumer form for the sample client.
| ||||||||||||||||||||||||||||||||||||
4. | |||||||||||||||||||||||||||||||||||||
5. |
Change all references to localhost1 to the name of the Web Reference created in step 2. |
Before we analyze the code, take a moment to execute the application and click each of the Search buttons. You will notice that each of the Search buttons has a different user experience. As mentioned earlier in this chapter, the synchronous button leaves the user waiting, not knowing what to expect. On the other hand, the asynchronous call allows the application to notify the user of the progress of the process being executed.
cmdSearchSync_Click() is the subroutine used for the synchronous execution of the WebMethod (Lines 7—14). You should be familiar with the code in this method because this call is like many of the other calls seen in earlier chapters of this book.
cmdSearchAsync_Click() is the subroutine used for the synchronous execution of the WebMethod (Lines 15—30).
On line 19, you can see that we declared an AsyncCallback object. This object allows us to declare a Callback function (Line 23) that will receive the results from the Web Service method. Next, an IAsyncResult object is declared. This object is used as the return value for the BeginFlightSearch WebMethod and allows the WebMethod to be executed asynchronously.
Within the call to BeginFlightSearch (Lines 24–27), there are a few important aspects to take note of with respect to the method call. You will notice that the first four parameters correspond with the first four parameters of the FlightSearch WebMethod that appears in Listing 8.1. Next, you will notice that the fifth and sixth parameters were passed in as Nothing. In the Web Service declaration, these parameters (Flight and Time) are declared by reference (ByRef). They are used to return results from the WebMethod. Since the thread that is executing the BeginFlightSearch will not be continuous, the results can not be returned to the variables directly. The next thing that you will notice is that the last two parameters were not included in the FlightSearch WebMethod listing in Listing 8.1. These parameters are added to the asynchronous methods automatically. The first parameter is a reference to the Callback method, while the second is a reference to the Web Service itself. After the WebMethod has been called, the timer control is enabled and displays an ongoing counter while the method is being executed by the Web Server.
SearchCallBack (Lines 32–42) is the subroutine passed into the BeginFlightSearch call (Lines 24–27). This subroutine will be invoked by the Web Service as soon as the execution has been completed. The AsyncState returned from the IAsyncResult object (Line 36) returns the object that was provided as the last parameter of the BeginFlightSearch call. A final call is then made to the EndFlightSearch method (line 37) to retrieve the results from the asynchronous execution. In addition to the IAsyncResult variable, two additional parameters are passed into the EndFlightSearch method to receive the results of the method's execution. You will notice that apart from the IAsyncResult parameter that is added by the proxy generator, only parameters declared ByRef are returned in the EndFlightSearch method.
To summarize, following are the steps for creating an application that calls a Web Service method asynchronously using the callback mechanism:
1. |
Create an application from which you want to access a WebMethod asynchronously. |
2. |
Add a Web reference to the Web Service. |
3. |
Implement a callback method. |
4. | |
5. |
Within the method that calls the WebMethod, create an AsynCallback object that will act as a wrapper to the callback method. |
6. |
Call the Begin<WebServiceMethodName> method exposed by the proxy, passing in the callback method as a parameter. |
7. |
Continue to perform additional operations while waiting for the execution of the WebMethod to complete. |
8. |
An alternative to using a callback function when calling Web Services asynchronously is to use the WaitHandle methods of the IAsyncResult.AsyncWaitHandle class. The WaitHandle methods make it possible to make asynchronous calls and then wait for these calls to complete. When using the WaitHandle class, the client can also specify a timeout. When the timeout is met, the WaitHandle will expire and the application flow will be returned to the thread. The WaitHandle class exposes three different variations, which are listed in Table 8.3.
Method | Description |
---|---|
WaitHandle.WaitOne | Blocks the current thread until the current WaitHandle receives a signal |
WaitHandle.WaitAny | Waits for any of the elements in the specified array to receive a signal |
WaitHandle.WaitAll | Waits for all of the elements in the specified array to receive a signal |
To make multiple asynchronous calls simultaneously, use either WaitHandle.WaitAny or WaitHandle.WaitAll. If the process should not continue until all of the asynchronous calls have been made, use WaitHandle.WaitAll. This will allow the thread to be blocked while multiple calls are being executed.
If it is enough for only one of the simultaneous calls to be completed or if each returning call should be processed as it returns, use the WaitHandle.WaitAny. The WaitHandle.WaitAny will indicate that a call has completed and will allow the client to identify the call.
To demonstrate this mechanism, we will begin by adding another button and another label to the form.
Add the controls listed in Table 8.4 to the form and set their properties as listed. The resulting form should look similar to Figure 8.2.
Control | Property | Value |
---|---|---|
Label | Name | lblResult2 |
Button | Name | cmdSearchAsyncWait |
Text | Async w Wait |
Double-click the cmdSearchAsyncWait button and add Listing 8.3 to the event's subroutine.
You may notice that Listing 8.3 starts very much like the cmdSearchSync method in Listing 8.2. The first difference that can be noticed is within the call to BeginFlightSearch; the last two parameters are passed in as Nothing (Lines 8–11). This is due to the fact that we are not interested in using a callback method in this case. Lines 12–24 demonstrate the ability to perform additional processing while the WebMethod is executing. After the additional processing is completed, the WaitOne method is used to wait for the WebMethod to complete its execution (Line 26). The WaitOne method will block indefinitely until the current instance receives a signal, and will freeze until the execution is completed.
After the WebMethod has completed the execution, the results may be retrieved by using the EndFlightSearch method, much like the SearchCallback method in Listing 8.2.
Run the application to see how this asynchronous mechanism executes.
How long should you wait for a WebMethod to return? This is a common question and it has a common answer—it depends. This might sound like an evasive answer, but the truth is that some methods take longer to execute than others. The person that will know best about how long to wait while a WebMethod executes is normally the developer (even though the end user usually is the one with the stopwatch). A common practice would be to execute the WebMethod multiple times with different amounts of data and different loads on the Web Server to see how long the method normally takes to execute. After a decision has been made on the maximum time that the method should be allowed to execute, you can make use of the WaitOne method of the AsyncWaitHandle object to specify how long the wait should be.
An overloaded version of the WaitOne method receives two parameters—timeout and exitContext. The timeout parameter is the number of milliseconds the method should wait for the thread to receive a signal. The exitContext parameter is set to true to ensure that the execution will exit the synchronization domain for the context before the wait (if in a synchronized context) and reacquire it.
To see how we can make use of this technique, we will add the following code to the cmdSearchAsyncWait_Click method that was added in the previous example. Replace the last four lines (excluding the End Sub command) with the code in Listing 8.4.
In Listing 8.3, Lines 12–24 simulate additional processing with a duration of 4 seconds. In Line 1 of Listing 8.4, a wait of 3 seconds is specified with the WaitOne(3000, True) method call.
You will notice that the duration of the simulation of additional work (4 seconds) and the wait (3 seconds) will exceed the execution time of the WebMethod (5 seconds). However, if you time the execution, you will notice that the execution takes exactly 5 seconds. When the AsyncWaitHandle object receives the execution completed signal, it will no longer wait for the entire timeout period but will continue with the execution of the method.
The WaitOne method will return a value of true if the current instance receives a signal that the execution has completed. (This is also known as waiting for an object to be signaled.) If, by the time the timeout expires the method has not received a signal, it will return a value of false.
If the method does not complete on time, it is good practice to Abort the asynchronous call. By calling the Abort method, exposed by the Web Service, the asynchronous call is stopped immediately.
To see what happens when the WebMethod takes longer to execute than the wait, change the timeout parameter to 500 milliseconds (equivalent to half a second).
Another variation to Listing 8.4 would be to replace the first line with the following two lines:
ar.AsyncWaitHandle.WaitOne(3000, True) If ar.IsCompleted Then
In this code, the IsCompleted method is used to verify whether the asynchronous call has been completed. true will be returned if the call has been completed; otherwise, false will be returned.
To summarize, the following steps create an application that calls a Web Service method asynchronously using the WaitHandle mechanism:
1. |
Create an application from which you want to access a WebMethod asynchronously. |
2. |
Add a Web reference to the Web Service. |
3. |
Create an instance of the proxy object. |
4. |
Create an interface to an AsyncResult and call the Begin<WebServiceMethodName> method exposed by the proxy, passing in the callback method as a parameter. |
5. |
Continue to perform additional operations while waiting for the execution of the WebMethod to complete. |
6. |
At a point where the results of the WebMethod are required, wait for the execution of the WebMethod to complete by using the WaitHandle to halt the processing. |
7. |
Many applications give the end user the power to decide if he or she would like to wait for a process to complete or cancel it. This power is given to the end user by means of a dialog that includes a Cancel button. There are many types of users—some that will use this power wisely and some that are just impatient and get fed up waiting for a process that takes more that a couple of seconds. For both of these types of users, it is very important to supply a clean way to cancel an asynchronous call.
In Listing 8.4, you saw that the FlightsSvc.Abort() command was used to abort a call to a WebMethod after it did not complete in time. The cancellation of a WebMethod by the user is very similar to the way that this is handled. (The timer in this case is in the user's hands.) There is one small difference. To abort an asynchronous WebMethod call, the scope of the variable used to reference the Web Service proxy needs to be changed. The Cancel subroutine needs to share the scope of the variable with the subroutine making the initial asynchronous call. This is so that the Abort call is being made to the same instance of the Web Service as the initial asynchronous call.
To demonstrate this, we will remove the Web Service proxy declaration from the cmdSearch_Click method (Listing 8.2, Line 28), and place it in the declarations section of the form. (It can be placed after line 4.)
Now, add an additional command button to the forms and rename it cmdCancel. Change the Text property of the button to Cancel. Double-click this new button and add the following to the event's code:
Private Sub cmdCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cmdCancel.Click FlightsSvc.Abort() Timer1.Enabled = False StatusBar1.Text = "Execution aborted." End Sub
Run the application and click the Search (Async) button. After a second or two, click the Cancel button. The asynchronous call will be cancelled and the user interface will display the Execution Aborted message.
Another issue that should be dealt with is the end user's ability to close the application at will. This too can cause unpredictable results and should be handled appropriately. In the case of a Windows Application, code that aborts an existing asynchronous call should be added to the form's Closing event handler.
In this section, we will take a look at how a Web Service can be consumed asynchronously by an ASP.NET WebForm.
This scenario is a little different from using Windows Forms, because there is no form resident in memory to catch the callback method when the method executed has completed. To get around this obstacle, we will use a technique that will cause the page to refresh itself and then check to see if any results have been returned. Listing 8.5 contains the HTML that will be used to display the ASP.NET WebForm.
Most of Listing 8.5 should be quite familiar to you. On lines 13–14, a <meta> tag is defined to instruct the browser to refresh the page contents at a predefined interval. In this case, the refresh rate has been set to 2 seconds. The <meta> tag has been defined as a server control so that the tag's visible status can be changed programmatically. By default, the tag's visible status will be set to false because, until a search has been executed, there is no reason to refresh the browser.
Lines 11–44 contain the Page_Load event subroutine. Depending on the current status, different blocks of code will be executed. The first check (Line 14) is made is to see if a search is in progress. If a search is not in progress, the page will load without performing any actions. If a search is in progress, a second check will be made (Line 16) to see if there is a result in the Session object. If no result is available, a message will be displayed to the user explaining that the system is still waiting for a response; the time lapsed since the execution of the search will also be displayed (Lines 17–22). The <meta> tag that deals with the browser refresh rate is set to visible, thus making sure that the browser will refresh itself at the appropriate moment (Line 24).
If a result is received, the block dealing with the result will be executed (Lines 25–42). The first action that is performed is that the <meta> tag that deals with the browser refresh rate is set to invisible (Line 27) and the results are retrieved from the EndFlightSearch method (Lines 34–25). The results are then displayed to the user and the various Session objects are cleared (Lines 37–28).
Lines 46–48 contain SearchCallback, which is the customary callback method for the WebForm.
Lines 50–66 contain the cmdSearch_Click event subroutine. This event is triggered when the user clicks the Search button on the form. The first action that takes place is that the <meta> tag that deals with the browser refresh rate is set to visible. A check is performed to see if a search is already in progress (Line 53). If no search is in progress, a search is executed, and the SearchCallback method is specified as the Callback method. The SearchInProgress key in the Session object is set to the current time (Line 63).