Things you might not know about Silverlight: WCF asynchrony pitfall

by Mister Goodcat 22. October 2010 08:33

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: , ,

Programming

Comments (9) -

10/23/2010 12:07:08 PM #

Daniel Vaughan

To perform blocking calls, the secret is not to use the UI thread. In case you're interested, I explored this some time ago.

www.codeproject.com/.../...hronousSilverlight.aspx

Cheers,
Daniel

Daniel Vaughan Switzerland |

10/23/2010 4:14:57 PM #

Mister Goodcat

Hi Daniel. Thank you for the link. That really was an interesting read, although my intention never was to make synchronous calls. When you're using the begin/end async pattern like you do, the result of my tests is the same (see my comment below in response to Josh Einstein).

Mister Goodcat United States |

10/23/2010 2:20:56 PM #

Josh Einstein

This is not specific to Silverlight or WCF. The async event pattern calls for the completed event to be raised on the UI thread (or more specifically, the current SynchronizationContext).

Instead of using DemoServiceClient, cast it to IDemoService and you'll be able to use the Begin/End pattern that does not perform it's own thread dispatching.

Josh Einstein United States |

10/23/2010 4:12:27 PM #

Mister Goodcat

Hallo Josh, thank you for your feedback.

The article does not describe all of my research. At first, I also suspected that the UI synchronization context is used. But when you analyze this in more detail, then you will find that in my example, when the WCF call is started from a separate thread, the runtime creates a separate synchronization context for that thread (not a UISynchronizationContext). Also, the completed event is not raised on the UI thread, just as one would expect (I added the Assert to demonstrate that). However, at some point in between, the runtime still marshals the call to the UI thread, no matter what you do.

I've also tried using the async begin/end pattern. I left that third sample out of the post because it has the same effect. When you call BeginXYZ on a separate thread and at the same time the UI thread is blocked you will experience that this call is blocked until the UI thread becomes responsive again. This really came as a surprise to me.

If you have more information or if you want me to send you the begin/end demo, let me know.

Mister Goodcat United States |

10/27/2010 2:08:28 PM #

Lewis Kirkaldie

I fell down into this hole over a year ago when I was chunking media fragments to feed into a MediaElement. It really is somewhat of a stinker, since it is totally possible to lock the UI thread if it is waiting for something from the background thread. Sometimes, syncronous patterns *are* justified and needed (like when blocking on data to arrive for a stalled media player).

In the end, i put a horrible kludge in place that put a heartbeat between the UI thread and the background thread, so the background thread knew if the UI thread was locked and would fail and retry it's web request. Not elegant, but it worked.

Lewis Kirkaldie United Kingdom |

10/28/2010 6:51:14 PM #

Prolay

Great Post. I enjoyed reading.

Prolay India |

7/8/2011 8:10:40 AM #

Tony Gray

I just blew three days down the same rabbit hole.  Thanks to you I won't spend a fourth.  Much appreciated.

Tony Gray Canada |

7/28/2011 5:23:29 PM #

meee

Does it help to let the client handle the request, not the browser?

msdn.microsoft.com/.../dd920295(v=vs.95).aspx

meee Germany |

7/28/2011 6:15:32 PM #

Mister Goodcat

Hi. I only mentioned this very briefly in the next to last paragraph - no, unfortunately this makes no difference. The sample code you can download even has a switch in it to enable/disable the client http stack, to demonstrate it behaves the same with both versions.

Mister Goodcat Germany |

Comments are closed

Archive