Почему Dispatcher.Invoke не выполняет аргумент делегата в этом примере? - PullRequest
1 голос
/ 29 сентября 2010
    [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 ]

5 голосов
/ 29 сентября 2010

Диспетчер может выполнять свои обязанности Begin / Invoke () только тогда, когда основной поток простаивает и снова входит в цикл диспетчеризации. В этот момент основной поток находится в состоянии покоя, и он может безопасно выполнять отправленные запросы. Как и любые уведомления, отправленные ему Windows.

В вашем случае он не простаивает, он застрял в Сне (2500).

0 голосов
/ 15 декабря 2013

Вот что я использую, у меня это работает, 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;
        }
    }
}

Примечания:

  • Протестировано в .NET 4.0Консольное приложение в VS 2012 - в зависимости от вашего модуля Unit Test Runner, YMMV.

  • В методе расширения DoEvents вы можете закомментировать один или другой из блоков try / catch (т.е. толькоделайте это способом WPF или только способом WinForms) - и он все равно будет работать - мне нравится иметь их обоих на всякий случай - если вы хотите сделать это способом WinForms: вам нужноДобавьте ссылку на System.Windows.Forms в ваш проект.

  • Thread.Yield / Thread.Sleep не требуются и не повышают ценность решения проблемы (ни сна, ниyield будет запускать любые события диспетчера в очереди), но они уменьшат использование ЦП в этом потоке (т. е. увеличит время автономной работы ноутбука, уменьшит скорость вращения вентилятора ЦП и т. д.) и будут лучше работать с окнами, чем если вы просто ждете, когда будете постоянно занятыпетля.Это также добавит некоторые накладные расходы, поскольку это время, которое могло бы быть потрачено на выполнение событий диспетчера очереди.

  • Вызов dispatcher.Invoke из того же потока, что и Диспетчер, кажется, просто вызываетметод, т. е. нет причины для вызова dispatcher.DoEvents() - однако, вызов dispatcher.BeginInvoke из того же потока не будет выполнять вызов немедленно, он будет ждать, пока вы не вызовете dispatcher.DoEvents().

0 голосов
/ 29 сентября 2010

Похоже, вы просто передаете его в качестве параметра 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);
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...