Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Taming Silverlight Async Calls

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
5 Dec 2011CPOL6 min read 24.3K   290   8   2
A helper class to simplify and enhance Silverlight async calls

Introduction

Users of Silverlight often fall foul of the Asynchronous calling pattern which is enforced by the framework. Among the pitfalls are:

  • Callback methods being called multiple times instead of once
  • Attempts to use information before it has been retrieved
  • Inability to link a specific call to a specific callback

A number of articles have been written on CodeProject and elsewhere about this issue. Indeed, there are even forthcoming changes to the language to make it easier to handle some common cases. However, all of the approaches I've seen to date either require you to redesign your approach, or enforce a 'pseudo synchronous' approach in which the benefits of having multiple async calls in flight simultaneously are lost.

The AsyncCalls library addresses these problems and more. It offers:

  • An easy to read way of writing simple 'pseudo sync' method calls
  • The ability to have several calls to the same method in progress at the same time, even allowing a different callback method to be run for each call, and guaranteeing that the user code will run exactly once for each async invocation
  • The ability to configure multiple calls to multiple async methods in such a way that as many of them as possible can be in progress at the same time, while guaranteeing that all needed results are available

And the whole library is only a page long - smaller than the demo Silverlight app included in the download!

Using the Code

Let's start with a simple situation. We have a simple Silverlight app with a button. Every time, we press the button, we go to a web service, run a potentially long-lived calculation, and when we get the result, we update a text field.

Our first ever attempt probably looks something like this (apologies to afficionados of MVVM, but for simplicity, everything here will be in code-behind style. If anything, the underlying model is even more valuable with MVVM).

C#
private void button_Click(object sender, RoutedEventArgs e)
{
// assume we have already created a client variable to hold our service reference
// AsyncSvc1Client client = new AsyncSvc1Client(); 
client.slowCompletedEventArgs += (s,e1) =>
 {
 textBox.text = e1.Result;
 }

 client.slowAsync("my parameter");
}

The first time you run this, it will work just fine. And it will continue probably to appear to work. However, every time you click the button, a new copy of the handler is added to the event handler, so after the 4th click, it will be called 4 times. Not a good idea, even though in this case you probably won't notice.

Most basic articles suggest two ways of addressing this issue.

The first is to move all the setup to a static location which is only ever called once. This works well when the callback doesn't need access to anything created later on. In our case though, the textBox will only be instantiated when we first enter the displayed page, so we have to add the method in the page initialiser, and since that can also be called many times, if you reenter the page, you have a similar problem, and need do to something messy using static methods or variables instead.

The second approach is to remove the call handler inside the async method. Unfortunately, in order to do that, we have to abandon the clarity of the inline handler, and create a separate named method we can explicitly remove. Something like:

C#
void myHandler(Object o, slowcall1CompletedEventArgs e) 
{
 client.slowCompleted -= myHandler;
 // Now do something useful
 // .......
}

Aside from the fact that by doing this we lose the clarity of an inline handler, this approach still won't work if you want to run the same method twice at once. Suppose you had two buttons, each of which updated a different text field with the result of a different call to the same function. What happens if both buttons are pressed before the first call returns.....

At this point, it's time to introduce my version of the above code, using my helper class. It looks like this:

C#
AsyncRunner<slowcallCompletedEventArgs> act1 =
       new AsyncRunner<slowcallCompletedEventArgs>();
act1.invoke = () =>
{
client.slowcallAsync(7, 1,act1);
};
act1.registerCallback(client, (o, e) =>
{
textBox1.Text = e.Result.ToString();
});
act1.initiateCall();

The method I'm calling - slowcall - takes two parameters. The first represents a delay in seconds, so we can see the effect of race conditions and waits. The second is just a number which the call returns as its result, so we can prove which invocation went where.

Now take a look at the code. Notice that it's pretty simple. Notice that we can show the call textually before the callback. Notice that the only piece of housekeeping we needed was the final parameter to the Async call, which is the name of the instance of the helper class.

This is the magic which allows us to keep track of which call goes where, using the vital, but little-known optional final parameter to the async service call, the so-called UserState which is passed unchanged from call to callback. Internally, we use the UserState to ignore any callbacks we receive which aren't from the specific call we made. This is the trick which allows multiple runs at once not to interfere with each other.

N.B. If you forget to specify the UserState, your callback will never get run.

The code above is already pretty useful. It's completely safe for it to live inside a button click or other method which gets called lots of times, and if you have two buttons, each can do the same thing with no risk of confusion.

Now let's consider a more complicated situation. Suppose we have three calls to make. Each of the first and second will take a long time, while the third depends on the first two results. What we want is something which kicks off the first two calls immediately but only starts the third call once the first two are done.

Suppose the first call takes 5 seconds, and the second call takes 7 seconds. If we run the calls pseudo-synchronously, by having each callback set up the next call, it will take 12 seconds before call 3 starts. If calls 1 and 2 run in parallel, it will only take 7 seconds before call 3 can start. Using an additional helper class AsyncCoordinator, we can get the parallellism we want as follows:

C#
 int passedFrom1To3 = -1;
 int passedFrom2To3 = -1;

 AsyncCoordinator coord = new AsyncCoordinator();

 AsyncRunner<slowcallCompletedEventArgs> act1 = 
    new AsyncRunner<slowcallCompletedEventArgs>(coord);
 act1.invoke = () =>
 {
 client.slowcall1Async(7, 1,act1);
 };
 act1.registerCallback(client, (o, e) =>
 {
 if (e.Error != null) return;
 passedFrom1To3 = e.Result;
 textBox1.Text = e.Result.ToString();

 });
 AsyncRunner<slowcallCompletedEventArgs> act2 = 
    new AsyncRunner<slowcallCompletedEventArgs>(coord);
 act2.invoke = () =>
 {
 client.slowcall1Async(3, 2, act2);
 };
 act2.registerCallback(client, (o, e) =>
 {
 if (e.Error != null) return;
 int result = e.Result;
 passedFrom2To3 = e.Result;
 textBox2.Text = result.ToString();
 });
 AsyncRunner<slowcallCompletedEventArgs> act3 = 
    new AsyncRunner<slowcallCompletedEventArgs>(coord);
 act3.invoke = () =>
 {
 // our invoker can refer to the result of call 2 safely
 client.slowcall1Async(1, passedFrom2To3, act3);
 };
 act3.registerCallback(client, (o, e) =>
 {
 if (e.Error != null) return;
 // our callback can also refer to the results of earlier calls safely
 int res = e.Result * 10 + passedFrom1To3;
 textBox3.Text = res.ToString();
 });
 AsyncRunner<slowcallCompletedEventArgs> act4 = 
    new AsyncRunner<slowcallCompletedEventArgs>(coord);
 
// define the mutual dependencies
 act3.dependsOn(act1);
 act3.dependsOn(act2);
 // Finally, kick it all off.
 coord.initiate();

The key things to note are:

  • We can use normal local variables to communicate results between calls
  • We can ensure that calls with dependencies don't happen too early, but do happen as soon as possible
  • The total overhead in terms of boiler-plate code is minimal. Almost everything we write is application logic, and it's all inside a single method

At this point, I encourage you to download and run the attached sample, which contains a full implementation of a slightly more complex example, as well as the all important definition of the classes which make it all work. If all you are looking for is a solution, you probably need go no further. If you want to understand how the code works, look out for a forthcoming article.

Points of Interest

The most interesting thing, for me, in coming up with this approach, was how little code was needed to make it all work. I suspect that in a language like F#, it would be even smaller...

The most frustrating aspect was the need to use reflection as the only way of effectively passing the event handler into the helper class. Why event handlers in C# are treated in such an awkward way beats me.

History

  • 4th December, 2011: First version fit for publication
  • 5th December, 2011: Fixed missing line before calling invoke method to prevent race condition and reuploaded source

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGreat job Pin
Ikado11-Dec-12 21:49
Ikado11-Dec-12 21:49 
Tanks for sharing this. My vote is 5
Error is human, computers only magnify it!

GeneralMy vote of 5 Pin
PCoffey5-Dec-11 3:50
PCoffey5-Dec-11 3:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.