Skip Navigation LinksHome / Articles / View Article

Tip: Asynchronous Silverlight - Execute on the UI thread

+ Add to SilverlightShow Favorites
7 comments   /   posted by Emil Stoychev on Jun 24, 2008
(6 votes)
Categories: Learn , Tutorials , Tips and Tricks

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

Share


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!

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

    posted by pwr on Sep 13, 2009 05:46
    This article's been translated into Russian for silverlight.su
  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by nz on Nov 05, 2009 00:31
    Hi Dennis, I try your solution, for some reason it is not asynchronous to the UI
  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by Tom on Apr 23, 2010 14:48
    Great post, Dispatcher.BeginInvoke worked perfectly for me!
  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by Doan Huynh on Jul 30, 2010 13:41
    Thank you, it is useful
  • RE: Tip: Asynchronous Silverlight - Execute on the UI thread  

    posted by Fruitcake on Aug 22, 2010 19:43

    Awesome !!

     

    Thanks for this sweet small example. Helped me.

Add Comment

 
 

   
  
  
   
Please add 6 and 8 and type the answer here:

Join the free SilverlightShow webcast 'Running Silverlight Outside the Browser and with Elevated Trust'. Sept 7th, 8 am - 9 am PDT.
In this live session Chris Anderson will cover configuring and debugging OOB mode, toast notifications, elevated trust, direct file access and much more.
Learn more | Register | See more webinars (hide this)