Dela via


Using the Dispatcher

 

Background

The typical Micro Framework Presentation application will have at least two threads. The first thread, which is explicitly created by the developer, usually handles I/O to one or more hardware peripherals. The second thread, created and managed implicitly by the CLR, handles all UI operations such as drawing of UI elements such as controls and windows. This UI thread is also known as the Dispatcher and its purpose is to access the UI elements in a thread safe manner.

This article will discuss several techniques for executing code on Dispatcher thread, but first it will take a look at a common mistake that many developers make.

The Timer Application

Figure 1 shows the main part of the Timer application source code. This should be familiar territory to most MF developers. It has a main that creates an application, the main window and finally starts the application by calling the Run method with main window object. The main window consists of a single Panel object that contains a single Text control. The text control will display the amount of time that has elapsed since the program started.

In the CreateWindow method a Timer object is created. The Timer object will be responsible for updating the timerText control every second. All of the samples in this article will implement a different Timer solution, so the main UI code can remain the same throughout the discussion.

public class TimerApp : Microsoft.SPOT.Application

{

    public static void Main()

    {

        TimerApp myApplication = new TimerApp();

        Window mainWindow = myApplication.CreateWindow();

        // Start the application

        myApplication.Run(mainWindow);

    }

    private Window mainWindow;

    private Text timerText;

    private Timer timer;

       

    public Window CreateWindow()

    {

        // Create a window object and set its size to the

        // size of the display.

        mainWindow = new Window();

        mainWindow.Height = SystemMetrics.ScreenHeight;

        mainWindow.Width = SystemMetrics.ScreenWidth;

        Panel panel = new Panel();

        timerText = new Text();

        timerText.Font = Resources.GetFont(Resources.FontResources.small);

        timerText.HorizontalAlignment = HorizontalAlignment.Center;

        timerText.VerticalAlignment = VerticalAlignment.Center;

           

        panel.Children.Add(timerText);

        // Add the text control to the window.

        mainWindow.Child = panel;

        // Set the window visibility to visible.

        mainWindow.Visibility = Visibility.Visible;

        // Create a new timer object and start it.

        timer = new Timer(timerText);

        timer.Start();

        return mainWindow;

    }

}

Figure 1 Timer Application

Figure 2 shows what the application looks like when run using the Sample Emulator from the SDK.

Emulator View

Figure 2 Emulator View

The Wrong Way

Here is the first implementation of the Timer object that was mentioned in the previous section. This version will simply spin off a thread and give that thread a reference to the Text control. The thread will update the Text control’s TextContent property then go to sleep for one second.

Figure 3 provides the code for the Timer object. The Timer object’s constructor caches a reference to the Text control and sets the start time for the Timer as a DateTime. The Start method simply spins off the thread. The UpdateTimerTextThread is the thread start method and it does the work of updating the Text control and then going to sleep.

public class Timer

{

    private Text textView;

    private DateTime start;

    private Thread timerThread;

    public Timer(Text text)

    {

        textView = text;

        textView.TextContent = new TimeSpan(0).ToString();

        start = DateTime.Now;

    }

    public void Start()

    {

    // Spin off a thread to update the timerText every second.

        timerThread = new Thread(new ThreadStart(UpdateTimerTextThread));

        timerThread.Start();

    }

    protected void UpdateTimerTextThread()

    {

        while (true)

        {

            textView.TextContent =

                ((TimeSpan)DateTime.Now.Subtract(start)).ToString();

            Thread.Sleep(1000);

        }

    }

}

Figure 3 Timer1 - Simple Thread

The rendering of the UI controls is done on the Dispatcher thread, which is different from the Timer’s thread. Since the worker thread is modifying a shared resource; in this case the Text control, this program will produce unexpected results. Experiments with this example produced three different outcomes.

1. The Text control never updated and always displayed “00:00:00”.

2. The Text control width never updated and displayed “00:00:…”, where the ellipsis were added automatically by the control renderer because the control width was too short.

3. The program functioned normally.

Programmers typically want their program to work correctly and to work correctly every time it is run. The next example will show how to fix this problem.

Synchronizing the Two Threads

Since the problem appears to be related to multiple threads some sort of thread synchronization method must be employed. The Dispatcher does not expose any sorts of locks that could be used, but it does expose a method to execute code on the Dispatcher thread; causing all access to the UI control to occur on the Dispatcher thread. Three items are needed to implement this solution.

1. A delegate to use as a callback for the Dispatcher.

2. A method to implement the delegate

3. A call to the Dispatcher.Invoke method

Figure 4 provides an implementation of all three requirements. The UpdateTextDelegate provides the delegate and the UpdateTimerText method implements the delegate. Notice that the UpdateTimerText method simply updates the Text control’s TextContent property.

public class Timer

{

    private Text textView;

    private DateTime start;

    private Thread timerThread;

    private delegate void UpdateTextDelegate();

    public Timer(Text text)

    {

    textView = text;

        textView.TextContent = new TimeSpan(0).ToString();

        start = DateTime.Now;

    }

    public void Start()

    {

    // Spin off a thread to update the timerText

        // every second.

        timerThread = new Thread(

        new ThreadStart(UpdateTimerTextThread));

        timerThread.Start();

    }

    public void UpdateTimerText()

    {

    textView.TextContent =

            ((TimeSpan)DateTime.Now.Subtract(start)).ToString();

    }

    protected void UpdateTimerTextThread()

    {

    while (true)

        {

            textView.Dispatcher.Invoke(

            new TimeSpan(0, 0, 1),

                new UpdateTextDelegate(UpdateTimerText));

            Thread.Sleep(1000);

        }

    }

}

Figure 4 Timer2 - Thread with Dispatcher

This solution also spins off a thread that causes the Text control to be updated with the new elapsed time every second. This one differs in that it calls the Invoke method of Text control’s Dispatcher object. In this case the Invoke method takes two arguments. In actuality the Invoke method takes three arguments. Here is the definition of Invoke as taken from MSDN.

Syntax

C#  

public Object Invoke (

         TimeSpan timeout,

         Delegate method,

         Object[] args

)

Parameters

timeout

The maximum amount of time the program will wait for the operation to finish.

method

A delegate to a method that takes multiple arguments, which is pushed onto the Dispatcher object's event queue.

args

[ParamArrayAttribute] An object to be passed as an argument to the specified method. This can be a null reference if no arguments are needed.

Return Value

The return value from the invoked delegate, or a null reference if the delegate has no return value.

Figure 5 Invoke Documentation

 

In the example here, the delegate does not take any arguments, so the third argument is omitted. This program only updates the Text control every one second, so it is willing to wait for the UpdateTimerText method to be invoked for one second before it times out. The timeout parameter is important because the Invoke method is synchronous, meaning that it will not return until the call to the delegate has completed.

Calls to the Dispatcher thread can also be performed asynchronously using the BeginInvoke method. The BeginInvoke method does not take a timeout parameter and returns a DispatcherOperation object. The DispatcherOperation can be used to monitor and control the invocation of your delegate method. For instance you can get the result from the call at a later time or abort the operation altogether.

The NewPresentation sample that ships in the .NET Micro Framework SDK v2.5 uses the asynchronous call BeginInvoke in the GPIO Button’s interrupt handler. This allows the interrupt thread to return right away to handle more button presses or other hardware input.

This solution works great and is not too hard to implement, but there is actually an easier method built into the Framework that will be discussed in the next section.

The DispatcherTimer Class

The DispatcherTimer is a timer that is fully integrated into the Dispatcher’s queue. Figure 6 provides an example that uses the DispatcherTimer. Notice that in the Timer.Start method a thread was not spun off as was done in past examples. Instead a DispatcherTimer object is instantiated and the Dispatcher for the Text control is passed in during object creation. Next, the Tick event is hooked up with our UpdateTimerText call back. Lastly, the DispatchTimer object’s Interval property is set to one second and the timer is started by calling the Start method. The Interval defines the period at which the timer event will fire; in this case one second.

The signature for UpdateTimeText had to change to match the EventHandler delegate definition, which takes two arguments. Again, the UpdateTimeText updates the Text control’s TextContent property and returns.

public class Timer

{

    private Text textView;

    private DateTime start;

    private DispatcherTimer dispatchTimer;

    public Timer(Text text)

    {

        textView = text;

        textView.TextContent = new TimeSpan(0).ToString();

       start = DateTime.Now;

    }

    public void Start()

    {

        // Create a dispatcher timer to update the timer text

        // every second.

        dispatchTimer = new DispatcherTimer(textView.Dispatcher);

        dispatchTimer.Tick += new EventHandler(UpdateTimerText);

        dispatchTimer.Interval = new TimeSpan(0, 0, 1);

        dispatchTimer.Start();

    }

    public void UpdateTimerText(object sender, EventArgs e)

    {

        textView.TextContent =

            ((TimeSpan)DateTime.Now.Subtract(start)).ToString();

    }

}

Figure 6 Timer3 - DispatcherTimer

The Temperature sample that shipped with the .NET Micro Framework 2.5 SDK contains a more complete example using the DispatcherTimer with simulated hardware.

Conclusion

All access to UI Elements, such as Windows, Panels and Controls must be done by the Dispatcher Thread. There are two methods to execute code on the Dispatcher thread. One method is to call the Invoke method for the UI Elements Dispatcher. The other method is to use the DispatcherTimer.

Comments

  • Anonymous
    March 04, 2008
    Background The typical Micro Framework Presentation application will have at least two threads. The first

  • Anonymous
    January 03, 2009
    Finalmente in questi giorni di “festa” sono riuscito a trovare qualche ora di calma da famiglia, figlie

  • Anonymous
    December 16, 2011
    The comment has been removed

  • Anonymous
    January 01, 2014
    Briljant! I had big performance problems. I have an engine that changes state fast, and the WPF GUI is a presentation of that. As my invoke (solution with invoke) updates the UI as fast as the thread can run. However, invoking on the UI thread is extremly slow.(500/1000 msec per update) The DispatcherTimer is much, much faster. (ten/twenty/hundred times faster). So, now my GUI can keep up with the actual engine model state, without slowing down my business logic thread (engine that calculates new percentages, that are presented in my bars and text controls). So, if you need performance, use the DispatcherTimer object (remember to use thread locking thoug!) I used public void UpdateTimerText(object sender, EventArgs e) {       lock(lockobject)       {            ....gui update code based on model state       } }

  • Anonymous
    January 22, 2015
    How come 2 threads be created for an application?

  • Anonymous
    January 28, 2015
    There is an example of two threads being created in an application in one of the earlier blogs blogs.msdn.com/.../threads-and-thread-priorities-in-netmf.aspx Hope that helps.