次の方法で共有


A Simplified Asynchronous Call Pattern for WinForm Applications

I have written a number of smart client applications recently that employ some form of asynchronous call behavior to prevent the UI freezing while the application makes a web service call in the background. Now, it’s true that the .NET framework provides a generic pattern for making asynchronous calls, but I find that this is sometimes a little unwieldy to use from within a WinForm application, mainly because of threading issues.

In this article I’ll describe how you can implement a simpler asynchronous call pattern which allows you to consume web services from a WinForm application without having to worry about threads ever again.

Service Agents

Visual Studio .NET generates a nice web service proxy class which allows the web service to be called asynchronously, but this proxy class implements the generic .NET framework asynchronous call pattern which, as I illustrate below, is rather inconvenient to use from a WinForm application. For this reason, amongst others, I don’t usually use the generated proxy classes directly but instead employ an intermediate ‘Service Agent’ class.

Service agents are classes which provide additional functionality which help a client interact with a web service. Service agents can implement many useful features, such as data caching, security credential management, offline operation support, etc. In this article, I am going to describe a service agent class that provides a much simpler asynchronous call pattern than that provided by the generated proxy class.

I could, of course, have built the additional functionality into the generated proxy class directly, but I like to leave the proxy class exactly as generated by Visual Studio and only change it by hand when absolutely necessary. Apart from anything else, this prevents me from losing code when I refresh the web reference! The service agent class uses the generated web service proxy class to make the actual web service calls.

The UI Thread

An application starts off with one thread which is used to create and manage the user interface. This thread is called, quite ingeniously, the UI thread. A developer’s natural instinct is to use the UI thread for everything, including making web service calls, remote object calls, calls into the database, etc, and this can lead to big usability and performance issues.

The problem is, you can never reliably predict how long a web service, remote object or database call will take, and if you make such a call on the UI thread then there will come a time when the UI will lock up and you will have irritated the user no end.

Naturally, you would want to do these kinds of calls on a separate thread but I would go further and say that you should do all non-UI related tasks on a separate thread --- I am firmly of the opinion that the UI thread should be used solely for managing the UI and all calls to objects where you can’t absolutely guarantee sub-second (or better) response times should be asynchronous, be they in-process, cross-process or cross-machine.

In any case, to help make asynchronous calls easier to handle from the UI thread, I have been playing with a simplified asynchronous call pattern which looks something like the one which will be available in Whidbey but which you can use now. To start with, let’s examine how the normal .NET framework asynchronous call pattern works.

The .NET Asynchronous Call Pattern

An object which supports the.NET framework asynchronous call pattern, such as a generated web service proxy class, has a Begin and an End method for each exposed web method. To make an asynchronous call, a client calls the Begin method which returns immediately, or at least once it has setup a separate thread to make the actual web service call. At some later point in time, the client calls the End method once the web service call has been completed.

When does the client know when to call the End method? The Begin method returns an IAsyncResult object which you can use to track the progress of the asynchronous call and you could use this object to explicitly wait for the background thread to finish, but doing this from the UI thread would defeat the whole point of doing the work synchronously. A better approach, for a process with a user interface, is to register a callback so that you will get notified automatically when the work has completed.

Let’s look at some example code. In this example, say we want to retrieve some customer data from a web service asynchronously. We have a web service proxy object and it supports a web method GetCustomerData. We can start the web service call and register for a callback using the following code, which we assume is invoked on the UI thread in response to some user interaction with the application’s user interface.

private void SomeUIEvent( object sender, EventArgs e )

{

// Create a callback delegate so we will

// be notified when the call has completed.

AsyncCallback callBack = new

AsyncCallback( CustomerDataCallback );

// Start retrieving the customer data.

_proxy.BeginGetCustomerData( “Joe Bloggs”, callBack, null );

}

Where CustomerDataCallback is the method which will get called when the web service call finally returns. In this method, we need to call the End method on the web service proxy object to actually retrieve the customer data. We might implement this method like this…

public void CustomerDataCallback( IAsyncResult ar )

{

// Retrieve the customer data.

_customerData = _proxy.EndGetCustomerData( ar );

}

Now, it is important to note that this method will be called on the background worker thread. If we want to update the UI with the newly obtained data (say we want to update a data grid control to display the customer data) we have to be careful to do this on the UI thread. If we don’t, then all manner of strange things may happen and we will have a difficult to diagnose bug to fix later on.

So how do we switch threads? Well, we can use the Invoke method which all Control derived objects implement. This allows us to specify a method which will be called on the UI thread and that method is where we can safely update our user interface. To use the Control.Invoke method we have to pass a delegate to our UI update method. Our CustomerDataCallback method would then look something like this

public void CustomerDataCallback( IAsyncResult ar )

{

// Retrieve the customer data.

_customerData = _proxy.EndGetCustomerData( ar );

// Create an EventHandler delegate.

EventHandler updateUI = new EventHandler( UpdateUI );

// Invoke the delegate on the UI thread.

this.Invoke( updateUI, new object[] { null, null } );

}

The UpdateUI method might be implemented like this.

private void UpdateUI( object sender, EventArgs e )

{

// Update the user interface.

_dataGrid.DataSource = _customerData;

}

Now while this is not exactly rocket science, I find the need for making such a ‘double hop’ is needlessly complicated. The problem is that the original caller of the asynchronous method (i.e., the WinForm class in this example) is made to be responsible for switching threads and this requires using another delegate and the Control.Invoke method.

A Simplified Asynchronous Call Pattern

One technique that I employ quite often to lessen the complexity, and the amount of code required, to making asynchronous calls is to factor the thread switching and delegate stuff into an intermediate class. This makes it really easy to make asynchronous calls from the UI class without ever having to worry about such things as threads and delegates. I call this technique ‘Auto Callback’. Using this technique, the above example would look like this…

private void SomeUIEvent( object sender, EventArgs e )

{

// Start retrieving the customer data.

_serviceAgent.BeginGetCustomerData( “Joe Bloggs” );

}

Once the web service call completes, the following method is invoked automatically.

private void GetCustomerDataCompleted( DataSet customerData )

{

// This method will be called on the UI thread.

// Update the user interface.

_dataGrid.DataSource = customerData;

}

The name of the callback method is inferred from the name of the original asynchronous call (so there is no need for constructing and passing a delegate), and it is guaranteed to be called on the correct thread (so there is no need to use Control.Invoke). Much simpler and a lot less error prone.

Now, there’s no such thing as a free lunch so where is all the magic code which enables this much simpler model? Well, it’s built into the ServiceAgent class which looks like this.

public class ServiceAgent : AutoCallbackServiceAgent

{

private CustomerWebService _proxy;

// Declare a delegate to describe the autocallback

// method signature.

private delegate void

GetCustomerDataCompletedCallback( DataSet customerData );

public ServiceAgent( object callbackTarget )

: base( callbackTarget )

{

// Create the web service proxy object.

_proxy = new CustomerWebService();

}

public void BeginGetCustomerData( string customerId )

{

_proxy.BeginGetCustomerData( customerId,

new AsyncCallback( GetCustomersCallback ), null );

}

private void GetCustomerDataCallback( IAsyncResult ar )

{

DataSet customerData = _proxy.EndGetCustomerData( ar );

InvokeAutoCallback( "GetCustomerDataCompleted",

new object[] { customerData },

typeof( GetCustomersCompletedCallback ) );

}

}

The service agent is not hard to write in this case and does not require much code. It is fully re-usable so we would only need to write it once and we can use it from any WinForm class. We are effectively shifting the responsibility for managing the threading issues away from the developer of the client code to the developer of the object which provides the asynchronous call API, in this case the service agent, which is where it belongs. We can of course use this technique on any object which provides an asynchronous API.

The AutoCallbackServiceAgent base class is a simple generic class which implements the InvokeAutoCallback method. It looks like this…

public class AutoCallbackServiceAgent

{

private object _callbackTarget;

public AutoCallbackServiceAgent( object callbackTarget )

{

// Store reference to the callback target object.

_ callbackTarget = callbackTarget;

}

protected void InvokeAutoCallback( string methodName,

object[] parameters,

Type delegateType )

{

// Create a delegate of the correct type.

Delegate autoCallback =

Delegate.CreateDelegate( delegateType,

_callbackTarget,

methodName );

// If the target is a control, make sure we

// invoke it on the correct thread.

Control targetCtrl = _callbackTarget as

System.Windows.Forms.Control;

if ( targetCtrl != null && targetCtrl.InvokeRequired )

{

// Invoke the method from the UI thread.

targetCtrl.Invoke( autoCallback, parameters );

}

else

{

// Invoke the method from this thread.

autoCallback.DynamicInvoke( parameters );

}

}

}

The above code creates a delegate to the callback method and then decides whether to invoke it on the calling thread or the UI thread. If the invocation target is a control derived object, then it will invoke the callback method on the UI thread if required.

For those interested in such details, if you look at the above code closely, you might see that it can be simplified significantly if we didn’t have to specify the auto callback delegate in the derived class. If we didn’t need to specify the signature of the callback delegate we could handle pretty much everything automatically and the derived service agent class would only need to implement the single line of code in the BeginGetCustomerData method.

Why do we need to specify this delegate? Well it turns out that if we want to use the Control.Invoke method we need a delegate. It is unfortunate that the .NET framework developers didn’t provide a version of this method which took a MethodInfo object because that would have made life a lot easier when writing generic code such as that above.

An alternative approach would be to specify a well known delegate type and use it for all callback method signatures. For instance, we could mandate that all auto callback methods have a method signature which takes a generic array of objects and pass the web service parameters back to the client that way. The delegate declaration might look like this…

public delegate void AutoCallback( object[] parameters );

Using this delegate would mean that we can dramatically simplify the service agent code but would have to cast the returned data to the expected type in the client code.

Hmmmm… Is It Worth It?

Is it worth all the hassle to implement a service agent class like this? Well, it depends on how much you want to simplify the lives of the UI developers. Writing a service agent class like the one above doesn’t necessarily mean less code but it does represent a better distribution of responsibilities between the UI and service agent developers.

As well as providing a simpler asynchronous call pattern, we can add other interesting functionality to the service agent class. I’ll be building on these basic service agent ideas in the future to show how you might implement some interesting functionality into the service agent, such as automatic local data caching, etc. Building such functionality into the service agent class means that the UI developer has a lot less to worry about.

Also, in a future article, I’ll show how you can customize the web service proxy generation process in Visual Studio .NET so that you can generate service agent classes automatically.

Copyright © David Hill, 2003.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

Comments

  • Anonymous
    December 12, 2003
    Can this be extended/used with Web form application ???
    -Manish
    manishgo_in@hotmail.com
  • Anonymous
    December 13, 2003
    As described, this technique will not work from a web form because the calling object has to be around when the asynchronous call finishes so the callback method can be invoked. For a web form this will not be case (unless you block the thread, which you should never do!) because it is a transient object just created to service a single web request.

    There is a great article on MSDN which describes what you need to do in the WebForm case...

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service07222003.asp

    So, if you are calling a web service, or any kind of lengthy call, from an ASPX page, then you need to do the call asynchronously from within a PreRequestHandler. It's all a bit complicated but the performance gains are worth it...


  • Anonymous
    December 14, 2003
    Thanks a lot David for the link and explanation...
    -Manish
  • Anonymous
    January 07, 2004
    Generics will simplify the service agent.
    In the mean time, perhaps one of the "templated" code generators could be used.
  • Anonymous
    February 19, 2004
    Patterns and Pattern Languages are ways to describe best practices, good designs, and capture experience in a way that...
  • Anonymous
    March 18, 2004
    Saw your article on MSDN - (looks promising, I gave you an 8), and the ServiceAgent class looks like a good candidate for code generation based on reflection of the underlyin WebService proxy class.

    But - Is there a typo/bug in the ServiceAgent example code? Sholdn't the call in GetCustomerDataCallback be to GetCustomerDataCompletedCallback and not to GetCustomersCompletedCallback?

    Thanks,

    Oskar
  • Anonymous
    March 19, 2004
    I like your article. And in fact part of the idea is being implemented in my app in hand.

    But my problem is that, there is no way, from the GUI point of view, to catch exceptions raised by the webservice proxy. Because it's now intercepted by the ServiceAgent. Probably it's possible to supply a callback for failed webservice invoke. But then that complicates the interface and looks ugly. Do you have any idea of how to solve this problem? Any way to route the exceptions back to the callback function supplied by the GUI class?

    -Derek
  • Anonymous
    April 13, 2004
    I saw the article on msdn and it worked fine. It would fit perfectly into my CFW - Project. Anybody tried to get it working?
    (no Delegate.CreateDelegate )

    Mathias Fritsch
  • Anonymous
    April 30, 2004
    BackgroundWorker for .NET 1.1 (www.idesign.net) can be used to solve the same problem.
  • Anonymous
    June 08, 2004
    Just musing, do MS pay you when they republish your copyrighted work on MSDN, or do they simply offer you the fame/glory? :)
  • Anonymous
    July 02, 2004
    The comment has been removed
  • Anonymous
    July 07, 2004
    If you can only access the database on a single thread, then you have a few options.

    You could use the UI thread to update and query the database, passing data from the background thread to the UI thread for it to update the UI and access the local database. This is not very elegant and using the UI thread to update and access a database is generally to be avoided, but if the database is local then you might get away with it.

    The alternative is to create a single background thread which is responsible for managing the database and then use this to query and update the database. Both the UI and other background threads will interact with this manager thread to get data into and out of the database.

    I guess a third option would be to close and re-open the connection for each time you access the database...
  • Anonymous
    July 12, 2004
    I am guessing this will not work in the .net compact framework. Is that true? Are there other options?


    thanks