[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)();
}
此代码块不会引发异常。同时,Dispatcher.Invoke不执行任何操作。我发现这很奇怪。
我将一个辅助方法提取到一个基础ViewModel中。工作线程使用此方法DoOnUIThread()来避免线程关联问题。 但是在我的单元测试中,我发现由于上述问题,尝试测试视图模型对象会导致失败。
我可以将整个行为转移到可插拔的依赖项中,我可以在测试中替换它。例如ViewModelBase依赖于UIThreadExecutor.Execute(Action),我使用的是一个只在我的测试中调用动作的假冒。但是我很好奇为什么Dispatcher的行为方式......
答案 0 :(得分:5)
当主线程空闲并重新进入分派循环时,Dispatcher只能执行其Begin / Invoke()任务。此时主线程处于静止状态,可以安全地执行调度的请求。以及Windows发送给它的任何通知。
在你的情况下,它没有空闲,它被困在Sleep(2500)内。
答案 1 :(得分:0)
看起来你只是将它作为参数传递给BackgroundWorker
。请尝试在SomeWork
函数中添加此内容:
public void SomeWork(object sender, DoWorkEventArgs e)
{
((MulticastDelegate)e.Argument).DynamicInvoke();
}
您可以尝试这样做而不是直接Sleep
:
while (worker.IsBusy)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,new EmptyDelegate(delegate{}));
//Application.DoEvents();
System.Threading.Thread.Sleep(10);
}
答案 2 :(得分:0)
这是我使用的,它对我有用,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;
}
}
}
备注:强>
在VS 2012中的.NET 4.0控制台应用程序中测试 - 取决于您的单元测试运行器,YMMV。
在DoEvents扩展方法中,你可以注释掉一个或另一个try / catch块(即只用WPF方式,或者只做WinForms方式) - 它仍然可以工作 - - 我想同时拥有它们,以防万一 - 如果你想以WinForms方式实现它:你需要在项目中添加对System.Windows.Forms的引用。
Thread.Yield / Thread.Sleep不是必需的,并且不会为解决问题增加值(sleep和yield都不会运行任何排队的调度程序事件) - 但它们会降低该线程上的CPU使用率(即更好的笔记本电脑电池续航时间,更安静的CPU风扇等)并且与Windows相比,如果您只是在一个永远繁忙的循环中等待,那么它的播放效果会更好。它还会增加一些开销,因为这可能会耗费时间来运行排队的调度程序事件。
从与调度程序相同的线程调用dispatcher.Invoke
似乎只是直接调用该方法,即没有理由调用dispatcher.DoEvents()
- 但是,从同一个调用dispatcher.BeginInvoke
线程不会立即执行调用,它会等到你调用dispatcher.DoEvents()
。