views:

54

answers:

2
    [Test]
    public void A()
    {
        var d = Dispatcher.CurrentDispatcher;

        Action action = () => Console.WriteLine("Dispatcher invoked me!");

        var worker = new BackgroundWorker();
        worker.DoWork += SomeWork;

        //worker.RunWorkerAsync( (Action) delegate { Console.WriteLine("This works!"); } );
        worker.RunWorkerAsync((Action) delegate { d.Invoke(action); } );

        System.Threading.Thread.Sleep(2500);
    }

    private void SomeWork(object sender, DoWorkEventArgs e)
    {
        (e.Argument as Action)();
    }

This block of code doesn't throw an exception. At the same time, Dispatcher.Invoke does nothing. I found that odd.

I extracted a helper method into a base ViewModel. Worker threads used this method DoOnUIThread() to avoid the thread affinity issue. However in my unit-tests, I find that attempting to test the view model objects results in failures due to the above issue.

I could move this whole behavior out into a pluggable dependency that I could substitute in my tests. e.g. ViewModelBase depends on UIThreadExecutor.Execute(Action) and I use a fake that just calls the action in my tests. However I'm curious as to why Dispatcher behaves the way it does..

A: 

It looks like your just passing it as a parameter to the BackgroundWorker. Try adding this in your SomeWork function:

  public void SomeWork(object sender, DoWorkEventArgs e)
  {
     ((MulticastDelegate)e.Argument).DynamicInvoke();
  }

Instead of a straight Sleep you could try this:

  while (worker.IsBusy)
  {
     Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,new EmptyDelegate(delegate{}));
     //Application.DoEvents();
     System.Threading.Thread.Sleep(10);
  }
SwDevMan81
yeah i missed that in the code snippet... updated. thx
Gishu
Ah ok cool. Yeah I think Hans has the right idea then.
SwDevMan81
@SwDev - there is no Application.DoEvents member in WPF and AFAIK the idea itself is not recommended.
Gishu
@Gishu - You are right.. sorry i updated it.
SwDevMan81
Found this post too... might be helpful: http://stackoverflow.com/questions/1106881/using-the-wpf-dispatcher-in-unit-tests
SwDevMan81
+1  A: 

Dispatcher can only perform its Begin/Invoke() duty when the main thread goes idle and re-enters the dispatch loop. At that point the main thread is quiescent and it can safely execute the dispatched requests. As well as any notifications sent to it by Windows.

It isn't idle in your case, it is stuck inside of Sleep(2500).

Hans Passant
So is there a way to start a message loop for unit-tests - the counterpart of Application.Run() for Wpf exes? Also isn't Invoke supposed to be synchronous / immediate ?
Gishu
@Gishu - You could loop on the IsBusy property of the BackgroundWorker. Not an idea solution, but it should work. See my edit.
SwDevMan81
@SwDev - that's a guaranteed deadlock if the BGW has a RunWorkerCompleted event. It cannot stop being IsBusy until the event runs, the event cannot run while you're in the loop testing IsBusy.
Hans Passant
@Gishu: why not use Application.Run? Yes, it is synchronous to the UI thread. No, it is not immediate because the UI thread must be idle.
Hans Passant
@Hans - that is a lot of baggage for a unit test for a ViewModel object, which is UI agnostic. Also App.Run() blocks the test runner thread. I guess I'd have to move this behavior out to a dependency..
Gishu