Multi-threaded Applications: Making Asynchronous calls to a .NET web service

The Business Case
I am currently designing a Customer Inquiry screen for our customer support personnel. I built a Windows application in VB.NET to retrieve and view large amounts customer data from different areas of the enterprise. Business requirements stated to return customer information, such as address and tax information, purchase history, and open orders.

I decided to create a Web Service to handle the functionality of retrieving customer details from the SQL Server. This way I could create a Windows application or a web application and use the same components. Even if a remote user wants to use a Windows application, the web service can retrieve customer data from the home office.

I will assume you are already familiar with the creation of a web service, if not this article maybe difficult to understand. I completed the functionality of my web service, what I call SEER services. The SEER services will forever be my one stop shop for customer information. Currently it performs five functions, including, searching by customer name or number, retrieve past invoice history, calculate customer spending by fiscal year, and retrieving open orders.

Creating and Calling a Multi-threaded Web Service
You don’t have to do anything special to create a multi-threaded web service. The .NET framework already handles this. For each of your WebMethods, .NET creates several more methods; giving your web service that asynchronous touch. Ok, let’s say I have a function called getInvoiceHistory, .NET has also created a method called BegingetInvoiceHistory and EndgetInvoiceHistory. Only calling this method is slightly different from calling the method normally. Let’s look at the differences.

I have previously declared a Dataset as dstDataSet, my web service as seerservices, and selectedCustomer is a string containing the customer ID who I am retrieving the data for. In my single threaded days, I would have simply retrieved customer data in this fashion.

dstDataSet = seerservices.getInvoiceHistory(selectedCustomer)

But in the asynchronous world that same method call would look something like this.

seerservices.BegingetInvoiceHistory(selectedCustomer, acCustomerRecords1, 0)

As you can see more parameters needs to be passed to our asynchronous method than the synchronous method. The first value is a string with the customer ID, same as before. But the next value is another object called AsyncCallback. I used the following declaration to create that object.

Private acCustomerRecords1 As AsyncCallback

        acCustomerRecords1 = New AsyncCallback(AddressOf Me.onCompletedCustomerRecords1)

This will allow a local function onCompletedCustomerRecords1 to handle the returning data. In order to complete the threaded call you will need to call EndgetInvoiceHistory, or Endxxxxxx where xxxxxx is your function name.

Private Sub onCompletedCustomerRecords1(ByVal asyncResult As IAsyncResult)
             'Declare the web service
            Dim seerservices As New SeerServices.seer
            Try
                'Returns the dataset to a class variable (why is explained later)
                _dstDataSet1 = seerservices.EndgetInvoiceHistory(asyncResult)
                'Bind the dataset to my datagrid
                Me.BeginInvoke(CallDataBindToDataGrid)
            Catch ex As Exception
                MsgBox(ex.Message, MsgBoxStyle.Critical, "Error!")
            End Try
        End Sub

Notice the function declares the web service and then calls the EndgetInvoiceHistory function. This will retrieve the return value from the function.

Summary
That’s it; to call an asynchronous web service you just follow a few steps. And before I tie up some loose ends, let’s go through these steps again.

1. Declare a AsyncCallback object

Private acCustomerRecords1 As AsyncCallback
acCustomerRecords1 = New AsyncCallback(AddressOf Me.onCompletedCustomerRecords1)

2. Call the thread friendly version of your web service function. Again you don’t have to do anything to your web service. This function is created automatically by .NET.

seerservices.BegingetInvoiceHistory(selectedCustomer, acCustomerRecords1, 0)

3. Create a function to handle the thread after completion.

Private Sub onCompletedCustomerRecords1(ByVal asyncResult As IAsyncResult)
        Dim seerservices As New SeerServices.seer
            Try
                _dstDataSet1 = seerservices.EndgetInvoiceHistory(asyncResult)
                Me.BeginInvoke(CallDataBindToDataGrid)
            Catch ex As Exception
                MsgBox(ex.Message, MsgBoxStyle.Critical, "Error!")
            End Try
      End Sub

Calling out of thread objects
Ok, now your probably wondering what Me.BeginInvoke means. This is a little off topic but I will through it briefly. DataBindToDataGrid is a method that will bind the class property _dstDataSet1 to a datagrid on my form. Now CallDataBindToDataGrid is a previously declared MethodInvoker object that will call the aforementioned function.

Dim CallDataBindToDataGrid As New MethodInvoker(AddressOf Me.DataBindToDataGrid)

The reason for this is, a datagrid is a single instance object and it’s methods cannot be accesses while in a thread. The BeginInvoke method will call the datagrid databinding properties and assign _dstDataSet1 as its data source. Try binding the dataset directly to the datagrid in this function, or even calling DataBindToDataGrid function directly and an exception will be thrown. The gist of the error is, “Controls created on one thread cannot be parented to controls created on another thread” or you can’t call methods of a single-threaded object outside your thread.

Public Sub DataBindToDataGrid()
        dgHistoryDetail.DataSource = _dstDataSet1
        dgHistoryDetail.DataMember = "invoiceheaders"
        _dstDataSet1 = Nothing
    End Sub

Conclusion
The main reason I chose this approach to data retrieval is it speeds up database access. Instead of each function retrieving and binding the data in order, each function is done simultaneously. And the fact that the UI is still responsive while the data is loading will make it easier on the user. You will be amazed by the fact you can still manuever around the user interface while your application is working in the background.

References
Griffiths, Ian. Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads. MSDN. Feb 2003. Aug 9, 2004. http://msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/

Meier, J.D., Srinath Vasireddy, Ashish Babbar, Alex Mackman. Improving Web Services Performance. Improving .NET Application Performance and Scalability. May 2004. Aug 9, 2004. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt10.asp

Leave a Reply