Tuesday, July 27, 2010

Wpf Dispatcher Misuse

Unlike the Highlander there can be more than one Dispatcher.  Just because you invokeSystem.Windows.Threading.Dispatcher.CurrentDispatcher does not mean you will always get the Dispatcher responsible for executing tasks on the logical tree.

Consider this example:

<Window 
    x:Class="WpfDispatcherInfalibility.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Height="75" 
    Title="MainWindow" 
    Width="100">

    <Grid>
        <TextBlock Text="{Binding Path=Field1}" />
    </Grid>

</Window>

namespace WpfDispatcherInfalibility
{
    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Threading;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : INotifyPropertyChanged 
    {
        private string field1 = "Default";

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            var thread = new Thread(this.WorkerMethod) { Name = "My worker thread 1" };
            thread.Start();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public string Field1
        {
            get
            {
                return this.field1;
            }

            set
            {
                this.field1 = value;
                this.RaisePropertyChanged("Field1");
            }
        }

        private void RaisePropertyChanged(string name)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        private void WorkerMethod()
        {
            // This will not retrieve the UI dispatcher because it is simply getting a dispatcher associated 
            // with this thread and this is not the UI thread. Just because you invoke Dispatcher.CurrentDispatcher
            // does not mean you will be given the dispatcher associated with the UI.
            var dispatcher = Dispatcher.CurrentDispatcher;
            string threadName = Thread.CurrentThread.Name;

            // The lambda will never be invoked because the dispatcher retrieved is not running.
            dispatcher.BeginInvoke(new Action(() => this.Field1 = "Field set from thread " + threadName));
        }
    }
}

As expected the UI shows the following window. And this window's text does not change.
What is the moral of the story?

Keep a reference to the dispatcher from a known point inside the controller for your xaml.  Or you can get it from any derivative of DispatcherObject (which is the root object of all Xaml components).

No comments:

Post a Comment