Thursday, May 15, 2014

Asynchronous WCF calls

I'm working on hooking my WPF Purchasing system to an existing, home-grown reporting system that was never intended to be called from a WPF application. The report generation is too slow to be done synchronously so I have to figure out how to make asynchronous WCF calls. Fortunately it's not too difficult.

I have already written the WCF endpoint called ExecuteCannedReport which takes a report id and returns a byte array containing the pdf representation of the report. Everything works except the call blocks the main program while the report is being created. The endpoint itself does not need to know if it is being called synchronously or asynchronously so nothing has to change there.

I started by modifying the service reference in the client so that the "Generate asynchronous operations" checkbox is checked ...


This makes Begin<service> and End<service> methods available. I can now invoke the service asynchronously like this...


    Private PurSvc As New PurchasingServiceReference.PurchasingClient
    -----------------------------------------------------------------

    PurSvc.BeginExecuteCannedReport(ReportID, AddressOf CannedReportFinished, Nothing)

Notice that the service reference (PurSvc) must be global because it must be reachable by the CannedReportFinished method that is called when the service completes.

The Begin<service> method adds two new parameters to the synchronous <service> method. The first new parameter is the address of the method that will be called when the service completes or errors. The second new parameter is an optional AsyncState which I'm not using. This line of code launches a new WCF request and immediately continues - thus it is non-blocking.

When the service completes or errors the CannedReportFinished method will be called with an IAsyncResult parameter. It might look something like this...

    Public Sub CannedReportFinished(Result As IAsyncResult)

        Dim CannedReport As PurchasingServiceReference.CannedReport = Nothing
        Try
            CannedReport = PurSvc.EndExecuteCannedReport(Result)
        Catch ex As Exception
            MessageBox.Show("Failed to create report: " & ex.Message)
        End Try

    End Sub

Calling End<service> with the correct IAsyncResult will block until that particular Begin<service> completes. The AsyncResult parameter allows you to specify which particular call you are waiting for. This allows you to architect your asynchronous calls is different ways. For example, you don't have to use a callback if you don't want to

If the endpoint raised an exception, the same exception will get raised again by the End<service> call, which can be trapped and processed as desired.

You might be tempted to update the UI in the callback. If you do so you will raise an exception that states that the UI object is owned by a different thread. Try this instead...

    Dispatcher.Invoke(Sub() UpdateWithResults())

    Public Sub UpdateWithResults()
        RTFButton.Visibility = Windows.Visibility.Visible
        PDFButton.Visibility = Windows.Visibility.Visible
        CSVButton.Visibility = Windows.Visibility.Visible
    End Sub

No comments:

Post a Comment