Calling a Web Service Asynchronously

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. Flights.asmx.vb Shows the Source Code for a Slow Web Service
Imports System.Web.Services
Imports System.Threading

Public Class Flights
    Inherits System.Web.Services.WebService

    <WebMethod()> Public Function FlightSearch( _
                                ByVal Departure As String, _
                                ByVal Destination As String, _
                                ByVal DepartureDate As Date, _
                                ByVal ReturnDate As Date, _
                                ByRef Flight As String, _
                                ByRef Time As String) As String
        'Simulate lengthy search...
        Thread.Sleep(5000)
        'Return a Flight Number
        Flight = "VF" & CInt(Rnd() * 100).ToString
        Time = "9:00 AM"
        FlightSearch = "VF" & CInt(Rnd() * 100).ToString
    End Function

End Class

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.

Using the Callback Function

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.


Table 8.2. Control Properties for the Consumer Form
Control Property Value
Label Name lblResult
Button Name cmdSearchAsync
 Text Search (Async)
Button Name cmdSearchSync
 Text Search (Sync)
Timer Name Timer1
 Interval 1000
StatusBar Name StatusBar1
StatusBarPanel1 Name StatusBarPanel1
 Text  
 ShowPanels False

4.
Add Listing 8.2 to the form.

5.
Change all references to localhost1 to the name of the Web Reference created in step 2.

Listing 8.2. Form1.vb Shows the Code That Will Be Used to Consume the Web Service Asynchronously
Private Sub cmdSearchSync_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) _
                                Handles cmdSearchSync.Click
  Dim FlightNumber As String
  Dim Time As String
  Dim svcFlightSearch As New localhost1.Flights()
  StatusBar1.Text = "Initializing execution..."
  svcFlightSearch.FlightSearch("Point A", "Point B", _
                              "10/12/2001", "12/12/2001", _
                              FlightNumber, Time)
  lblResult.Text = FlightNumber & " at " & Time
  StatusBar1.Text = "Synchronous Execution Completed"
End Sub

 Private Sub cmdSearchAsync_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) _
                            Handles cmdSearchAsync.Click
  Dim FlightsSvc As New localhost1.Flights()
  Dim cb As AsyncCallback
  Dim ar As IAsyncResult

  StatusBar1.Text = "Initializing execution..."
  cb = New AsyncCallback(AddressOf SearchCallback)
  ar = FlightsSvc.BeginFlightSearch("Point A", "Point B", _
                                    "10/12/2001", "12/12/2001", _
                                    Nothing, Nothing, _
                                    cb, FlightsSvc)
  seconds = 0
  Timer1.Enabled = True
 End Sub

Public Sub SearchCallback(ByVal ar As IAsyncResult)
  Dim FlightNumber As String
  Dim Time As String
  Dim FlightSvc As localhost1.Flights
  FlightSvc = ar.AsyncState
  FlightSvc.EndFlightSearch(ar, FlightNumber, Time)
  Timer1.Enabled = False
  StatusBar1.Text = "Async. Execution Completed (" _
                              & seconds.ToString & " second/s)"
  lblResult.Text = FlightNumber & " at " & Time
End Sub

  Private Sub Timer1_Tick(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles Timer1.Tick
      seconds = seconds + 1
      StatusBar1.Text = "Executing (" & _
                                   seconds.ToString & " second/s)"

  End Sub
						

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.
Within the callback method, make a call to the End<WebServiceMethodName> method exposed by the proxy. This method will return the results of WebMethod's execution.

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.
After the WebMethod finishes processing and returns the result to the proxy, the proxy will call the callback method.

Using the WaitHandle Methods

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.

Table 8.3. Possible WaitHandle Methods
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.

Figure 8.2. The consumer form with additional properties.


Table 8.4. Additional Controls and Their Properties for the Consumer Form
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.

Listing 8.3. Code that Handles the Click Event of the Command Button cmdSearchAsyncWait
Private Sub cmdSearchAsyncWait_Click(ByVal sender As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles cmdSearchAsyncWait.Click
  Dim FlightNumber As String
  Dim Time As String
  Dim FlightsSvc As New localhost1.Flights()
  Dim ar As IAsyncResult
  ar = FlightsSvc.BeginFlightSearch("Point A", "Point B", _
                                    "10/12/2001", "12/12/2001", _
                                    FlightNumber, Time, _
                                    Nothing, Nothing)
  'Simulate additional work that can be performed concurrently (4 secs)
  Dim StartTime As DateTime = DateTime.Now
  Dim curTime As DateTime = StartTime
  Do While (DateDiff(DateInterval.Second, StartTime, curTime) < 4)
    If curTime < DateTime.Now Then
      curTime = DateTime.Now
      StatusBar1.Text = "Executing Asynchronously (" _
                                & (DateDiff(DateInterval.Second, _
                                   StartTime, curTime) _
                                   + 1).ToString() _
                                & " second/s)"
    End If
  Loop
  StatusBar1.Text = "Waiting..."
  ar.AsyncWaitHandle.WaitOne()
  FlightsSvc.EndFlightSearch(ar, FlightNumber, Time)
  lblResult.Text = FlightNumber & " at " & Time
  StatusBar1.Text = "Async. Execution Completed"
End Sub
						

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.

Evaluating the Length of the Wait

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.

Listing 8.4. Code That Handles a Predetermined Wait
If ar.AsyncWaitHandle.WaitOne(3000, True) Then
  FlightsSvc.EndFlightSearch(ar, FlightNumber, Time)
  lblResult.Text = FlightNumber & " at " & Time
  StatusBar1.Text = "Async. Execution Completed"
Else
  lblResult.Text = "Please try again at a later time..."
  FlightsSvc.Abort()
  StatusBar1.Text = "Async. Execution Not Completed"
End If

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.
After the WebMethod finishes processing and returns the results, make a call to the End<WebServiceMethodName> method exposed by the proxy.

Permitting Users to Cancel a Call

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.

Handling Web Pages That Make Asynchronous Calls to WebMethods

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.

Listing 8.5. FlightSearchForm.aspx Is a Demo Flight Search Page That Calls the Web Service Asynchronously
<%@ Page Language="vb" AutoEventWireup="false"
    Codebehind="FlightSearchForm.aspx.vb"
    Inherits="VirtualFlightsWeb.FlightSearchForm"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
     <HEAD>
        <title>Flight Search Form</title>
        <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0"/>
        <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"/>
        <meta name="vs_defaultClientScript" content="JavaScript"/>
        <meta name="vs_targetSchema"
              content=http://schemas.microsoft.com/intellisense/ie5/>
        <meta http-equiv='refresh' content='2'
              visible="false" id="metaTag" runat="server"/>
    </HEAD>
    <body>
        <form runat="server">
            <P align="center">
               <b><asp:Label ID="lblMessage" runat="server" /></b>
               <br><br>
               <asp:Label id="lblMessage2" runat="server" align="center">
                   To perform a search - hit the search button.
               </asp:Label>
               <br><br>
               <asp:Button ID="cmdsearch" Text="Search" Runat="server"/>
            </p>
        </form>
    </body>
</HTML>

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.

Listing 8.6. FlightSearchForm.aspx.vb Shows the Code Behind Listing 8.5 (FlightSearchForm.aspx)
01: Imports System.Web.UI
02:
03: Public Class FlightSearchForm
04:     Inherits System.Web.UI.Page
05:
06:     Protected WithEvents lblMessage As WebControls.Label
07:     Protected WithEvents lblMessage2 As WebControls.Label
08:     Protected WithEvents cmdsearch As System.Web.UI.WebControls.Button
09:     Protected WithEvents metaTag As HtmlControls.HtmlGenericControl
10:
11:   Private Sub Page_Load(ByVal sender As System.Object, _
12:                         ByVal e As System.EventArgs) Handles MyBase.Load
13:     ' Checking is a search is currently in progress for this session
14:     If Not Session("SearchInProgress") Is Nothing Then
15:       'Checking if a result has yet to be returned
16:       If Session("Result") Is Nothing Then
17:         lblMessage.Text = "Still waiting...(" & _
18:                           DateDiff(DateInterval.Second, _
19:                                    Session("SearchInProgress"), _
20:                                    DateTime.Now) & _
21:                           " second/s)"
22:         lblMessage2.Visible = False
23:         'Enabling the refresh meta tag
24:         metaTag.Visible = True
25:       Else 'A result has been returned
26:         'No more need for browser refresh
27:         metaTag.Visible = False
28:         Try
29:           Dim FlightNumber As String
30:           Dim Time As String
31:           Dim FlightsSvc As New localhost.Flights()
32:           Dim ar As IAsyncResult
33:           'Retrieving the Asynchronous Result object
34:           ar = Session("Result")
35:           FlightsSvc.EndFlightSearch(ar, FlightNumber, Time)
36:           lblMessage.Text = FlightNumber & " at " & Time
37:           Session("SearchInProgress") = Nothing
38:           Session("Result") = Nothing
39:         Catch ex As Exception
40:           lblMessage.Text = "Still searching..."
41:         End Try
42:       End If
43:     End If
44:   End Sub
45:
46:   Protected Sub SearchCallback(ByVal ar As IAsyncResult)
47:     Session("Result") = ar
48:   End Sub
49:
50:   Private Sub cmdSearch_Click(ByVal sender As Object, _
51:                            ByVal e As System.EventArgs) Handles cmdsearch.Click
52:     metaTag.Visible = True
53:     If Session("SearchInProgress") Is Nothing Then
54:       lblMessage.Text = "Searching..."
55:       Dim FlightsSvc As New localhost.Flights()
56:       Dim cb As AsyncCallback
57:       Dim ar As IAsyncResult
58:       cb = New AsyncCallback(AddressOf SearchCallback)
59:       ar = FlightsSvc.BeginFlightSearch("Point A", "Point B", _
60:                                         "10/12/2001", "12/12/2001", _
61:                                         Nothing, Nothing, _
62:                                         cb, FlightsSvc)
63:       Session("SearchInProgress") = DateTime.Now
64:       lblMessage2.Visible = False
65:     End If
66:   End Sub
67: End Class
						

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.179.100