Using Closures to Simplify Asynchronous Programming


Url: http://wilderminds.blob.core.windows.net/downloads/asynchronousclo...

Silverlight Logo

When I teach Silverlight, some of the students lament at the requirement to make all network requests in an asynchronous manner. While I explained my opinion on the manner back in March of this year, I didn't get into how to make this easier. Lambdas and closures are the key to simplifying that code in my opinion. 

Quick caveat!  This does not work in Visual Basic (yet). VB10 has announced support for closures and lambdas that do not return values (e.g. Sub() instead of Function()), if you are using Visual Basic, I can't help you make it easier until VB10 ships.

Let's take the example of making a call to a simple web service.  I will show you two examples to try and get my idea across.  First we have a simple web service that returns a list of Game objects based on a single criterion (the genre).  Calling this service is simply a matter creating the client, registering the event handler and calling the asynchronous method:

// Create the client
GamesClient client = new GamesClient();

// Register the handler
client.GetGamesCompleted += 
  new EventHandler<GetGamesCompletedEventArgs>(client_GetGamesCompleted);

// Start the operation
client.GetGamesAsync("Shooter");

In the event handler we can just check the errors and set the result to our UI:

void client_GetGamesCompleted(object sender, GetGamesCompletedEventArgs args)
{
  // Complete the operation
  if (args.Error == null && !args.Cancelled)
  {
    theList.ItemsSource = args.Result;
  }
}

This code is pretty simple but we can use a lambda to avoid littering our code with unnecessary methods:

// Create the Client
GamesClient client = new GamesClient();

// Handle the event with a Lambda
client.GetGamesCompleted += (source, args) =>
{
  if (args.Error == null && !args.Cancelled)
  {
    theList.ItemsSource = args.Result;
  }
};

// Execute the Service Call
client.GetGamesAsync("Shooter");

Using the lambda (=>) creates an inline anonymous method for handling of the event.  Since this will only be used by our client and the client is short-lived (see issues with anonymous methods and long-lived objects here), this works well to simplify the code. 

This code becomes more difficult if you have to pass it data as developers tend to move that data to instance level (e.g. class level) variables to share it with the resulting code.  In other places (like AsyncCallback objects), they allow you to pass in a payload of data.  But this ends up with pretty ugly code.  For example, lets use the HttpRequest style of this same code;

// Interface to the HttpWebRequest-style interface
Games request = new GamesClient() as Games;

// Make the request
request.BeginGetGames("Shooter",
                      new AsyncCallback(OnGetGamesComplete), 
                      request);

In this style of the request, you would use a AsyncCallback to specify a method to callback and then pass in some state (in this case the request we made so we can use it in the callback. This results in the callback having to do some casting to get our object back;

void OnGetGamesComplete(IAsyncResult result)
{
  // Cast the passed in state
  Games theRequest = (Games)result.AsyncState;

  // Get the games
  IEnumerable games = theRequest.EndGetGames(result);
}

This grabbing of the state always annoyed me, but I figured it was necessary.  Or is it.  In this case our use of a lambda allows us to use a feature called closures.  With closures you can see local variables declared outside the anonymous method inside the method. For example:

// Interface to the HttpWebRequest-style interface
Games request = new GamesClient() as Games;

// Make the request
request.BeginGetGames("Shooter",
  new AsyncCallback(
    (result) =>
      {
        // Get the results
        IEnumerable<Game> games = request.EndGetGames(result);
      }
    ), null);

In this example we can use the request object create outside the anonymous method because the closure allows this.  This really simplifies the code. In fact, since the HttpRequest style is called on a non-UI thread, we can nest the lambdas and closures to call to the UI thread (using the Dispatcher, see my post on that here):

// Interface to the HttpWebRequest-style interface
Games request = new GamesClient() as Games;

// Make the request
request.BeginGetGames("Shooter",
  new AsyncCallback(
    (result) =>
      {
        // Get the results
        IEnumerable<Game> games = request.EndGetGames(result);

        // Use another Lambda and closure to update the list on the UI thread!
        Dispatcher.BeginInvoke( 
          () =>
            {
              theList.ItemsSource = games.ToList();
            }
          );
      }
    ), null);

Hopefully, this will help you in using the asynchronous model that is required in Silverlight 2. Note that this is the same as it is for non-Silverlight projects, but I find it really useful for students that have not had the opportunity to do asynchronous communication in their desktop applications.

You can download the code and play with it yourself.  Each of these methods are show in the project's page.xaml.cs file (see the #defines at the top of the file to switch between different versions):

AsynchronousClosures.zip




Application Name WilderBlog Environment Name Production
Application Ver 1.1.0.0 Runtime Framework .NETCoreApp,Version=v1.1
App Path D:\home\site\wwwroot Runtime Version .NET Core 4.6.25211.01
Operating System Microsoft Windows 6.2.9200 Runtime Arch X86