Christopher Bennage

Sponsors

The Lounge

Wicked Cool Jobs

Syndication

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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.


Posted 10-23-2008 11:42 AM by Christopher Bennage
Filed under: , ,

[Advertisement]

Comments

Dew Drop - October 24, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - October 24, 2008 | Alvin Ashcraft's Morning Dew
on 10-24-2008 9:17 AM

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

Jose wrote re: Tricksy Dispatcher & Running Tests in TDD.NET
on 11-05-2008 5:59 PM
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.
Christopher Bennage wrote re: Tricksy Dispatcher & Running Tests in TDD.NET
on 11-07-2008 10:40 AM

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

chris donnan wrote re: Tricksy Dispatcher & Running Tests in TDD.NET
on 11-13-2008 11:10 AM
Exactly this happened to me -google + you = help, thanx
Christopher Bennage wrote re: Tricksy Dispatcher & Running Tests in TDD.NET
on 11-13-2008 11:20 AM

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

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)

CodeBetter.Com