2010-09-29 33 views
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)(); 
    } 

该代码块不会引发异常。同时,Dispatcher.Invoke什么都不做。我发现那很奇怪。为什么Dispatcher.Invoke在此示例中不执行委托参数?

我提取了一个辅助方法到基础ViewModel。工作线程使用此方法DoOnUIThread()来避免线程相关性问题。 但是在我的单元测试中,我发现试图测试视图模型对象导致失败,原因是上述问题。

我可以将这整个行为移出一个可插入的依赖项,我可以在我的测试中替代。例如ViewModelBase依赖于UIThreadExecutor.Execute(Action),我使用一个假的只是在我的测试中调用动作。然而,我很好奇,为什么分派器行为的方式...

回答

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); 
    } 
+0

是的,我错过了在代码片段...更新。thx – Gishu 2010-09-29 19:39:00

+0

啊好凉快。是的,我认为汉斯当时有正确的想法。 – SwDevMan81 2010-09-29 19:40:45

+0

在WPF和AFAIK中没有Application.DoEvents成员,因此不推荐这个想法。 – Gishu 2010-09-29 19:54:34

5

调度员只能执行其开始/ Invoke的()的责任和重新进入调度循环。此时主线程处于静止状态,它可以安全地执行分派的请求。以及Windows发送给它的任何通知。

它不是闲置在你的情况下,它卡在睡眠(2500)内。

+0

那么是否有一种方法可以为单元测试启动一个消息循环 - 与Wpf exes的Application.Run()相对应?也不是Invoke应该是同步/即时? – Gishu 2010-09-29 19:48:02

+0

@Gishu - 您可以在BackgroundWorker的IsBusy属性上循环。不是一个想法的解决方案,但它应该工作。看我的编辑。 – SwDevMan81 2010-09-29 19:49:30

+0

@SwDev - 如果BGW有一个RunWorkerCompleted事件,那么这是一个有保证的死锁。在事件运行之前,它无法停止处于IsBusy状态,当您在循环测试IsBusy时,事件无法运行。 – 2010-09-29 19:52:20

0

下面是我用的东西,它的作品对我来说,情况因人而异:

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不是必需的,并且不会增加解决问题的价值(睡眠和yield都不会运行任何排队的调度程序事件) - 但它们会减少该线程上的CPU使用率(即更好的笔记本电脑电池寿命,更安静的CPU风扇等),并与Windows更好地发挥,如果你只是在一个永远忙碌的循环等待。它也会增加一些开销,因为这可能会花费在运行排队的调度程序事件上。

  • 在同一个线程调用dispatcher.Invoke作为调度员似乎只需要直接调用该方法,即没有理由骂dispatcher.DoEvents() - 然而,在同一个线程调用dispatcher.BeginInvoke不会立即执行调用,它会等到你请致电dispatcher.DoEvents()