Devlico.Us
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @devlicious

Christopher Bennage

Our WPF book is now available!
follow @bennage on Twitter!
 


Tricksy Dispatcher & Running Tests in TDD.NET

Updated 11/07/2008

I recently ran into an interesting situation. I had an ObservableCollection bound to my UI in WPF, and I needed to updated this collection on something other than the UI thread. In other words, I was adding items to a UI-bound collection on a different thread, which triggered the change notification events on that non-UI thread. Unfortunately, you can't do that, the change notification events for ObservableCollection can only be execute on the UI thread.

Karl Hulme blogged about this and posted some custom collections he wrote to deal with the problem. Karl's solution was heavier (and more generic than I needed), so I distilled his code down to this:

public class BindableCollection<T> : ObservableCollection<T>
{
    private readonly Dispatcher _dispatcher;

    public BindableCollection(Dispatcher dispatcher)
    {
        if (dispatcher == null) throw new ArgumentNullException("dispatcher");

        _dispatcher = dispatcher;
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        _dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => base.OnPropertyChanged(e)));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        _dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => base.OnCollectionChanged(e)));
    }
}

This really a simplified version of what Karl did, but it was all I needed. When you instantiate this collection, you pass in the dispatcher for the UI. You can easily get that using System.Windows.Threading.Dispatcher.CurrentDispatcher. In case you are not familiar with it, Dispatcher is WPF class for managing the queue of work items for a thread.

It's a sweet trick. To sum up, the collection is now dependent on a Dispatcher. We pass into it the Dispatcher for the UI thread. Then we override the change notification methods, so that we marshal the actual underlying change notification back onto the UI thread.

The cast to an Action looks a bit weird, but unfortunately lambdas are not implicitly cast to Actions.

The Unexpected Bug

I guess all bugs are unexpected, so whatever.

Notice the similarity in colors between Flash and Ayende's blog? Coincidence? A few of our automated tests dealt with this new BindableCollection, and everything was green when I executed the tests through ReSharper's test runner. Likewise, everything ran as expected in the application itself. However, when running these test through TDD.NET the _dispatcher.Invoker call never returned.

Right now, it's important to give credit where it is due. Ayende solved this problem before I had time to fully grasp it even. If there is a Justice League of .NET, Ayende is Flash (and we think Justice Gray is probably Superman? I mean, the hair, right?).

Anyway, errors in this explanation are my own.

Diagnosis

When you call Invoke() the action is placed onto a queue. The  processing of the queue is triggered by the method WndProcHook(). This method is the hook for processing message from the OS.  In other words, the queue is only processed each time the window receives a message.

Now TDD.NET executes tests in a console, ReSharper's test execute in a GUI. So with TDD.NET, the queue is never processed and hence the Invoke method never returns.

The Revision

Ayende came up with the following revision to our collection. You can see some functional influence here:

public class BindableCollection<T> : ObservableCollection<T>
{
    private readonly Action<Action> _dispatcher;

    public BindableCollection(Action<Action> dispatcher)
    {
        if (dispatcher == null) throw new ArgumentNullException("dispatcher");

        _dispatcher = dispatcher;
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        _dispatcher(() => base.OnPropertyChanged(e));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        _dispatcher(() => base.OnCollectionChanged(e));
    }
}

Instantiating this, might look like this:

new BindableCollection<stuff>(action => dispatcher.Invoke(DispatcherPriority.Normal, action));

However, in my tests, I can avoid the dispatcher altogether and simply:

new BindableCollection<stuff>(action => action());

Hope someone else finds this useful.



Comments

Dew Drop - October 24, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - October 24, 2008 | Alvin Ashcraft's Morning Dew

# October 24, 2008 9:17 AM

Jose said:

Chris, can you explain a little bit more the scenario that you needed this for? I assume the collection is binding to a UI control and therefor the events are fired on the UI thread right? what is it that you need it todo: handle the collection changed events in diferent thread? Also can you or were you trying to add/remove items to the collection in diferent thread while it was binded to the UI? if not how can this be done? having a controld binded to a collection, while a non UI thread modifies the collection, and the the control will update accordingly? thanks.
# November 5, 2008 5:59 PM

Christopher Bennage said:

@Jose I have updated the post to try to answer your questions.

# November 7, 2008 10:40 AM

chris donnan said:

Exactly this happened to me -google + you = help, thanx
# November 13, 2008 11:10 AM

Christopher Bennage said:

@chris I'm glad I could help! :-)

# November 13, 2008 11:20 AM

About Christopher Bennage

Christopher is a software developer and consultant at Blue Spire Consulting, a company he co-founded with Rob Eisenberg in 2006. He is a Christian, a marginal musician, and an armchair philosopher. His interests include programming, liberal education, science, truth, beauty, and a number of deceased British authors (C. S. Lewis, G. K. Chesterton, and most recently Owen Barfield.) He lives in Tallahassee, FL with his wife and three children and still prefers to play as the Night Elves in WarCraft 3. Check out Devlicio.us!

Our Sponsors

Proudly Partnered With