Reputation: 136613
[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..
Upvotes: 1
Views: 2845
Reputation: 6416
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()
.
Upvotes: 0
Reputation: 49978
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);
}
Upvotes: 0
Reputation: 941377
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).
Upvotes: 5