为什么Dispatcher.Invoke在此示例中不执行委托参数?

时间:2010-09-29 19:02:02

标签: c# .net wpf unit-testing

    [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的行为方式......

3 个答案:

答案 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()