1
    [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..

Gishu
  • 134,492
  • 47
  • 225
  • 308

3 Answers3

5

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
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 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 Sep 29 '10 at 19:48
  • @Gishu - You could loop on the IsBusy property of the BackgroundWorker. Not an idea solution, but it should work. See my edit. – SwDevMan81 Sep 29 '10 at 19:49
  • @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 Sep 29 '10 at 19:52
  • @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 Sep 29 '10 at 19:53
  • 1
    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 Sep 29 '10 at 20:06
0

Here's what I use, it works for me, YMMV:

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Windows.Threading;

namespace DispatcherFun
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            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)(() =>
            {
                d.Invoke(action);
            }));

            while (worker.IsBusy)
            {
                Dispatcher.CurrentDispatcher.DoEvents();
                Thread.Yield();
                Thread.Sleep(50);
            }
        }

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

        // Based On: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherframe.aspx
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public static void DoEvents(this Dispatcher dispatcher)
        {
            if (dispatcher != null)
            {
                // This is the "WPF" way:
                try
                {
                    DispatcherFrame frame = new DispatcherFrame();
                    dispatcher.Invoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
                    Dispatcher.PushFrame(frame);
                }
                catch { /* do nothing */ }

                // This is the "WinForms" way (make sure to add a reference to "System.Windows.Forms" for this to work):
                try
                {
                    dispatcher.Invoke(System.Windows.Forms.Application.DoEvents, DispatcherPriority.Send);
                }
                catch { /* do nothing */ }
            }
        }

        private static object ExitFrame(object f)
        {
            try
            {
                ((DispatcherFrame)f).Continue = false;
            }
            catch { /* do nothing */ }

            return null;
        }
    }
}

Notes:

  • Tested in a .NET 4.0 Console App in VS 2012 -- depending on your Unit Test Runner, YMMV.

  • In the DoEvents extension method you can comment out one or the other of the try/catch blocks (i.e. only do it the WPF way, or only do it the WinForms way) -- and it will still work -- I like to have them both, just in case -- if you want to do it the WinForms way: you'll need to add a reference to System.Windows.Forms to your project.

  • The Thread.Yield / Thread.Sleep are not required, and do not add value towards solving the problem (neither sleep nor yield will run any queued dispatcher events) -- but they will decrease CPU usage on that thread (i.e. better laptop battery life, quieter CPU fan, etc.) and play more nicely with windows than if you're just waiting in a forever busy loop. It will also add some overhead, as that's time that could've otherwise been spent running queued dispatcher events.

  • Calling dispatcher.Invoke from the same thread as the Dispatcher seems to just call the method directly, i.e. no reason to call dispatcher.DoEvents() -- however, calling dispatcher.BeginInvoke from the same thread will not execute the call immediately, it will wait until you call dispatcher.DoEvents().

BrainSlugs83
  • 6,214
  • 7
  • 50
  • 56
0

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
  • 48,814
  • 22
  • 151
  • 184
  • there is no Application.DoEvents member in WPF and AFAIK the idea itself is not recommended. – Gishu Sep 29 '10 at 19:54
  • Found this post too... might be helpful: http://stackoverflow.com/questions/1106881/using-the-wpf-dispatcher-in-unit-tests – SwDevMan81 Sep 29 '10 at 20:24
  • Application.DoEvents works fine here -- you just have to add a reference to System.Windows.Forms -- and then you call it via: "System.Windows.Forms.Application.DoEvents();" – BrainSlugs83 Dec 14 '13 at 21:57
  • And where is this "EmptyDelegate" class -- I can't find it anywhere? – BrainSlugs83 Dec 14 '13 at 21:58
  • 1
    @BrainSlugs83 - Its just an empty definition: `private delegate void EmptyDelegate();` – SwDevMan81 Dec 16 '13 at 16:47
  • Thanks. I recommend that you include that in your code snippet -- or use a lambda (like `(Action)(() => { })` ) which doesn't need to be defined. (As the meaning of "Empty" was quite ambiguous here.) – BrainSlugs83 Dec 19 '13 at 01:51