I know what you downloaded last summer Micro-Sprints – How to get things done as a developer

Things you might not know about Silverlight: WCF asynchrony pitfall

Published on Friday, October 22, 2010 6:33:00 AM UTC in Programming

Silverlight generally tries to make programming easier, and most of the time does pretty well doing so. The drawback of this sometimes is that when you want to create a more complex, not so common scenario, you may run into issues that really are not obvious. Sometimes, hidden deep behind the scenes of a simple programming model, unexpected things may happen that can give you a lot of headache when you dare to wander from the path.

WCF in particular is such a case where really a lot of the complex details are concealed from you, and when you read about the following you'll probably be surprised by some of the mechanics that happen when you do a seemingly simple web service call.

The scenario

The first time I stumbled upon this problem was when someone was trying to implement a heartbeat scenario. That means he wanted to periodically send a small piece of data to a given service which required this sort of "keep alive" pings from clients to keep track of them.

The problem was that when he was using a simple dispatcher timer that sends these heart beats in its tick event, reliability heavily depended on what was going on in the UI. Animations and other computationally intensive operations on the UI thread lead to hiccups with the web service calls, sometimes to a degree that the client was "dropped" by the server because of missing heart beats.

An obvious solution for someone who is familiar with multi-threading is to simply make the code that sends the heart beats execute on a separate thread that runs in parallel to the UI thread. That thread would not be affected by operations that happen in the UI and could therefore send the required data packets a lot more reliably. That's exactly what the person tried. And to his amazement, he had the same problems as before. Long operations on the UI thread stalled the service calls on the other thread. Huh?

A simple example

I've created a simple example to show what you would expect and what it should be like. In that example you can:

  • Start a dispatcher timer to raise periodic events on the UI thread.
  • Start a separate thread that also executes periodic actions not dependent on the UI.
  • Stall the UI thread by using a simple call to Thread.Sleep().

Both the timer and separate thread write the current time to a list box so we can see what happens. Obviously the second thread uses Dispatcher.BeginInvoke() for that, because a) it cannot access the UI directly and b) BeginInvoke() (as opposed to Invoke()) because it works in a fire-and-forget manner which means the second thread will not be affected by stalling the UI thread.

Here is the more interesting part, the code of the second thread. First how we start it:

private void StartThreadButton_Click(object sender, RoutedEventArgs e)
{
    // some UI manipulation
    StartThreadButton.IsEnabled = false;
    StallSecondThreadButton.IsEnabled = true;

    // reset the isRunning flag
    _isRunning = true;

    // start the thread
    ThreadStart ts = new ThreadStart(DoSomething);            
    _secondThread = new Thread(ts);
    _secondThread.IsBackground = false;
    _secondThread.Name = "Second Thread";
    _secondThread.Start();
}

As you can see, nothing fancy happens here. Some UI elements are disabled to prevent starting the thread a second time, and then the actual thread is created and started. Here is what it does:

private void DoSomething()
{
    while (_isRunning)
    {
        // simulate some pause
        Thread.Sleep(1000);

        // create a new message
        string message = string.Format("{0} - {1}", 
            DateTime.Now, 
            "Background tick");

        // let the UI add the new message to the list box
        Dispatcher.BeginInvoke(() =>
            {                        
                AddMessage(message);
            });
    }
}

Once every second, the thread becomes active, creates a new message that contains the current time, and adds the message to a list box using the dispatcher.

For the sake of completeness, here is the code regarding the dispatcher timer and for stalling the UI thread:

#region UI Timer

private DispatcherTimer _uiTimer;

private void StartTimerButton_Click(object sender, RoutedEventArgs e)
{
    StartTimerButton.IsEnabled = false;

    // create and start a new timer
    _uiTimer = new DispatcherTimer();
    _uiTimer.Interval = TimeSpan.FromSeconds(1.0);
    _uiTimer.Tick += new EventHandler(UITimer_Tick);
    _uiTimer.Start();
}

private void UITimer_Tick(object sender, EventArgs e)
{
    // simply add the current time to the list box
    string message = string.Format("{0} - {1}", 
        DateTime.Now, 
        "UI Timer tick");

    AddMessage(message);            
}

private void StallUIThreadButton_Click(object sender, RoutedEventArgs e)
{
    // we simulate this by a simple call to Thread.Sleep()
    Thread.Sleep(5000);
}

#endregion

The timer also just adds the current time to the list box, and we simulate the stalling with a simple call to Thread.Sleep().

When you run the application and start both the timer and second thread, you'll see output like this:

sample_output

The interesting part is when you stall the UI. The updates of the list box will stop to show for five seconds (that is how long we let the UI thread sleep), but when the UI thread returns to normal, outstanding updates will show immediately.

sample_output_stalled

The interesting parts you can see here is:

  • Stalling the UI thread did prevent the UI timer ticks from happening. You can see a gap from 07:54:53 to 07:54:59 in these UI timer ticks.
  • Stalling the UI thread however did not prevent the ticks of the second thread from happening. As you can see all the ticks are listed, without any gaps at the time the UI thread was stalled.

So far, so good. This is exactly what one would expect.

You can download the sample code here: MultithreadingTest.zip (22.36 kb)

Now on to WCF

Let's now alter the sample to use a WCF service. To that end, I only added a Silverlight-enabled WCF service to the web application, with a single method that bounces the argument:

[OperationContract]
public string BounceMessage(string message)
{
    return message;
}

In the Silverlight client, the complete dispatcher timer part from above stays the same. The second thread however now looks like this:

private void DoSomething()
{
    // create a new service client
    DemoServiceClient client = new DemoServiceClient();

    // hook the event
    client.BounceMessageCompleted += Client_BounceMessageCompleted;

    while (_isRunning)
    {
        // simulate a pause
        Thread.Sleep(1000);

        // create a message containing the current time
        // and send it to the service
        string message = string.Format("{0} - {1}", DateTime.Now, "Background tick");
        client.BounceMessageAsync(message);
    }                        
}

private void Client_BounceMessageCompleted(object sender, DemoServiceReference.BounceMessageCompletedEventArgs e)
{
    // we really do not expect this to happen on the UI thread
    bool isOnUIThread = Dispatcher.CheckAccess();
    Debug.Assert(!isOnUIThread);

    // let the UI show the bounced message from the service
    Dispatcher.BeginInvoke(() =>
    {
        AddMessage(e.Result);
    });
}

What it does is: every second, it creates the same tick message as before, but this time sends it to the WCF service. The service just bounces it back to the client, where it is added to the UI in the same manner as before. I've also added a simple check to the completed event handler of the service call to make sure we are not on the UI thread.

When we run the application and start both the dispatcher timer and the second thread, first we get a similar behavior like before:

wcf_output

But if we stall the UI thread this time, the following happens:

wcf_output_stalled

That is unexpected! Stalling the UI thread this time also prevented the second thread from operating. Apparently no service calls have been sent during the time the UI thread was unresponsive.

You can download the sample project here: WcfMultithreadingTest.zip (37.28 kb)

What's happening?

It took me a while to realize what is happening. Although both the call to the web service and the event handler that is executed when the call returns do not happen on the UI thread, the runtime marshals the execution of the request through the UI thread. The result is that even though from our point of view we did everything correctly to decouple the WCF calls from the UI, our second thread is not able to operate independently. When the UI thread is blocked, it also cannot continue to execute.

Here is a stack trace of the involved actions when you make a WCF service call (I reversed the order to make it chronological from top to bottom and removed unneeded details). First we can see our own code, then the transition to the generated code for the service reference, and eventually execution will be taken over by various classes in the System.ServiceModel namespace:

stack_trace_01

After running through a whole lot of methods in the ServiceModel namespace, you'll eventually see where the transition back to the UI thread happens, when the Http request is executed:

stack_trace_02

Consequences and possible solutions

The obvious consequence of this is that no matter how hard you try, your service calls will always rely on the responsiveness of the UI thread. This behavior however has more impact than you might initially realize. In our sample code, we were using a boolean flag to keep the second thread running. Now let's assume that you want to shut down this second thread gracefully at some point, and use the following code:

_isRunning = false;
_secondThread.Join();

What this is trying to do is signal to the other thread that it should stop, and then wait until it actually has stopped execution. Usually this should be no problem. In our WCF example however, when the second thread is about to send a service call, the call to Join() will result in a dead lock and your whole application will appear unresponsive. The reason for this is that then the UI thread waits for the second thread to finish, whereas the second thread waits for the UI thread to send the service call. You can try that in the sample project, it's actually not unlikely to happen.

These really are not obvious errors, even for experienced developers, because you would simply not expect that deeply hidden in the runtime code, some marshaling to the UI thread happens. Unfortunately, I did not find a real solution for this. I tried various attempts, like using the client Http stack, but I could not prevent that transition from happening. If someone else knows more about this, feel free to comment or contact me.

As a result, I really see no point in having WCF calls execute on a separate thread, unless of course you need to do some long and/or complex computational operations to prepare the data required to do these calls - then it might be beneficial to use a separate thread. In that case, you have to keep the above in mind though, to avoid introducing subtle multi-threading bugs in your application. On the other hand, if you have a scenario where you have time-critical aspects related to networking calls (like in the initial scenario), you have to move to lower-level APIs in Silverlight that don't show the demonstrated behavior. In particular, one option is to use sockets.

Tags: Multi-threading · Silverlight · WCF