Product Spotlight
(0 votes)

Tip: Asynchronous Silverlight - Execute on the UI thread

2 comments   /   posted by Emil Stoychev on Jun 24, 2008

I've been playing with some timers and Web Services and I got stuck in a case where the background thread was trying to update the UI. Well, that's never going to happen. To update the UI you should use the UI thread. Let's see how we can call the UI thread from the background thread.

Consider this situation:

You have data that needs to be updated every minute. The data is loaded by calling a web service.

For the update you can use the Timer class. Both the timer and the web service calls are executed asynchronously.

   1: ...
   2: Timer t = new Timer( GetData, null, TimeSpan.Zero, new TimeSpan( 0, 1, 0 ) );
   3: ...
   4: 
   5: public void GetData( object stateInfo )
   6: {
   7:     SampleWebServiceSoapClient client = new SampleWebServiceSoapClient();
   8:     client.HelloWorldCompleted +=
   9:         new EventHandler<HelloWorldCompletedEventArgs>( client_HelloWorldCompleted );
  10:     client.HelloWorldAsync();
  11: }
  12: 
  13: private void client_HelloWorldCompleted( object sender, HelloWorldCompletedEventArgs e )
  14: {
  15:     // update the UI
  16: }

Unfortunately this code doesn't work. If you try to update the UI in the callback you will get an error like "Invalid cross-thread access".

So what we should do to make this code work? At least two ways to go here:

1. Use the Dispatcher class to call the UI thread

2. Instead of using System.Threading.Timer use the DispatcherTimer

Dispatcher class

The Dispatcher class greatly simplifies calling the UI thread with a static method BeginInvoke:

Dispatcher.BeginInvoke( Action );

This way, with just a line of code, you can update the UI. Let's consider a TextBlock.Text property that we want to be updated in the client_HelloWorldCompleted callback:

   1: private void client_HelloWorldCompleted( object sender, HelloWorldCompletedEventArgs e )
   2: {
   3:     Dispatcher.BeginInvoke( () => lastUpdated.Text = DateTime.Now.ToLongTimeString() );
   4: }

The Dispatcher class guarantees that this code will be executed on the UI thread. For more information about the Dispatcher read this MSDN article

DispatcherTimer

The DispatcherTimer is a special timer integrated into the Dispatcher queue that is reevaluated on every Dispatcher loop. That makes it perfect for our case. Let's see how we can change the code so instead of using the Threading.Timer to use the DispatcherTimer.

   1: ...
   2: DispatcherTimer t = new DispatcherTimer();
   3: t.Interval = new TimeSpan( 0, 0, 1 );
   4: t.Tick += new EventHandler( RefreshData );
   5: t.Start();
   6: ...
   7: 
   8: private void RefreshData( object sender, EventArgs e )
   9: {
  10:     SampleWebServiceSoapClient client = new SampleWebServiceSoapClient();
  11:     client.HelloWorldCompleted +=
  12:         new EventHandler<HelloWorldCompletedEventArgs>( client_HelloWorldCompleted );
  13:     client.HelloWorldAsync();
  14: }
  15: 
  16: private void client_HelloWorldCompleted( object sender, HelloWorldCompletedEventArgs e )
  17: {
  18:     lastUpdated.Text = DateTime.Now.ToLongTimeString();
  19: }

The DispatcherTimer runs on the same thread as the Dispatcher and guarantees that this code will be executed on the UI thread.

Summary

Using the Dispatcher is an universal method to execute code on the UI thread. However sometimes, as in the shown example, we can simplify our code even more. So choose carefully when you encounter such situations.

Hope this helps.

References

Build More Responsive Apps With The Dispatcher

Dispatcher class on MSDN

DispatcherTimer class on MSDN



Comments

Comments RSS RSS
  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by Dennis on Jun 29, 2008 08:25

    Hi,

    Do you also have an example on parametrized asynchronous calls?

    I have tried this piece of code:

     private delegate void UpdateUIDelegate(TextBox tbx, string text);
           
            private void SetText(TextBox tbx, string text)
            {
                UpdateUIDelegate action = new UpdateUIDelegate(UpdateUI);
                tbx.Dispatcher.BeginInvoke(action,tbx,text);           
            }

            private void UpdateUI(TextBox tbx, string text)
            {
                tbx.Text = text;
            }
           
            void btn_Click(object sender, RoutedEventArgs e)
            {
                //field.SolveRecursively(0);
                foreach (UIElement elem in LayoutRoot.Children)
                {
                    TextBox tbx= elem as TextBox;
                    if (tbx != null)
                    {
                        Thread thread = new Thread(new ThreadStart(SetText));
                        thread.Start();
                        Thread.Sleep(10);
                    }
                }
            }

    But I get "No overload for 'SetText' matches delegate 'System.Threading.ThreadStart' "

  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by emil on Jun 29, 2008 15:26

    Hi Dennis,

    For parameterized call you can use ParameterizedThreadStart(void (object) target) instead of ThreadStart(void () target).

    So instead of

    Thread thread = new Thread( new ThreadStart( ... ) );

    use

    Thread thread = new Thread( new ParameterizedThreadStart( SetText ) );

    where SetText takes one parameter:

    private void SetText( object o )
    {
     
    }

    Another way, my preferred, to achieve your goal is the following:

    ThreadStart ts = delegate()
    {
        DispatcherOperation op = 
            Dispatcher.BeginInvoke( 
                new Action<TextBox, string>( UpdateUI ), 
                tbx, 
                "custom text" );
    };

    So your final code would look like:

    private void UpdateUI( TextBox tbx, string text )
    {
        tbx.Text = text;
    }
     
    void btn_Click( object sender, RoutedEventArgs e )
    {
        //field.SolveRecursively(0);
        foreach ( UIElement elem in lr1.Children )
        {
            TextBox tbx = elem as TextBox;
            if ( tbx != null )
            {
                ThreadStart ts = delegate()
                {
                    DispatcherOperation op = 
                        Dispatcher.BeginInvoke( 
                            new Action<TextBox, string>( UpdateUI ), 
                            tbx, 
                            "custom text" );
                };
     
                Thread thread = new Thread( ts );
                thread.Start();
                Thread.Sleep( 10 );
            }
        }
    }

    Hope that helps!

Add Comment