Понимание диспетчера WPF.BeginInvoke

У меня создалось впечатление, что dispatcher будет следовать приоритету операций, поставленных в очередь, и выполнения операций на основе приоритета или порядок, в котором операция была добавлена ​​в очередь (если такой же приоритет) пока мне не сказали, что это не так в случае WPF UI dispatcher.

Мне сказали, что если операция в потоке пользовательского интерфейса занимает больше времени, скажем, что чтение базы данных диспетчер UI просто пытается выполнить следующий набор операций в очереди. Я не мог согласиться с этим, поэтому решил написать образец приложения WPF, который содержит кнопку и три прямоугольника, при нажатии кнопки прямоугольники заполняются разными цветами.

<StackPanel>
    <Button x:Name="FillColors" Width="100" Height="100" 
            Content="Fill Colors" Click="OnFillColorsClick"/>
    <TextBlock Width="100" Text="{Binding Order}"/>
    <Rectangle x:Name="RectangleOne" Margin="5" Width="100" Height="100" Fill="{Binding BrushOne}" />
    <Rectangle x:Name="RectangleTwo" Margin="5" Width="100" Height="100" Fill="{Binding BrushTwo}"/>
    <Rectangle x:Name="RectangleThree" Margin="5" Width="100" Height="100" Fill="{Binding BrushThree}"/>
</StackPanel>

и в коде

private void OnFillColorsClick(object sender, RoutedEventArgs e)
{
    var dispatcher = Application.Current.MainWindow.Dispatcher;

    dispatcher.BeginInvoke(new Action(() =>
    {
        //dispatcher.BeginInvoke(new Action(SetBrushOneColor), (DispatcherPriority)4);
        //dispatcher.BeginInvoke(new Action(SetBrushTwoColor), (DispatcherPriority)5);
        //dispatcher.BeginInvoke(new Action(SetBrushThreeColor), (DispatcherPriority)6);

        dispatcher.BeginInvoke(new Action(SetBrushOneColor));
        dispatcher.BeginInvoke(new Action(SetBrushTwoColor));
        dispatcher.BeginInvoke(new Action(SetBrushThreeColor));

    }), (DispatcherPriority)10);
}

private void SetBrushOneColor()
{
    Thread.Sleep(10 * 1000);
    Order = "One";
    //MessageBox.Show("One");
    BrushOne = Brushes.Red;
}

private void SetBrushTwoColor()
{
    Thread.Sleep(12 * 1000);
    Order = "Two";
    //MessageBox.Show("Two");
    BrushTwo = Brushes.Green;
}

private void SetBrushThreeColor()
{
    Thread.Sleep(15 * 1000);
    Order = "Three";
    //MessageBox.Show("Three");
    BrushThree = Brushes.Blue;
}

public string Order
{
    get { return _order; }
    set
    {
        _order += string.Format("{0}, ", value);
        RaisePropertyChanged("Order");
    }
}

Прокомментированный код работает так, как ожидалось, методы вызываются на основе DispatcherPriority, и я также могу увидеть обновление экрана после завершения каждой операции. Order One, Two, Three. Цвета рисуются один за другим.

Теперь рабочий код, где DispatcherPriority не указан (Я предполагаю, что он по умолчанию равен Normal), порядок все еще One, Two, Three, но если я покажу a MessageBox внутри методов, всплывающее окно Thrid появится сначала, затем Two, затем One, но когда я отлаживаю, я мог видеть, что методы запускаются в ожидаемом порядке (IntelliTrace даже показывает, что отображается окно сообщения, но я не вижу его на экране в то время и вижу его только после завершения последней операции.) его просто, что MessageBox es показаны в обратном порядке.

Это потому, что MessageBox.Show является блокирующим вызовом, и операция очищается после того, как сообщение было закрыто.
Даже тогда порядок MessageBox также должен быть One, Two and Three`

Ответы

Ответ 1

Прежде чем перейти к вашему поведению кода, это является необходимым условием для понимания приоритетов Dispatcher. DispatcherPriority делится на диапазоны, как показано на рисунке ниже.

DispatcherPriority

Если вы просто ставите 4 действия на 4 выше диапазона на Dispatcher. очередь Foreground будет выполнена сначала, затем Background и затем в последней очереди Idle. приоритет 0 не будет выполнен.

Теперь ваш код:

Три задачи ставятся в очередь 1-й в Background, 2-й в Background и 3-й в Foreground очереди. Итак, третий будет выполнен в первую очередь. то вторая задача вызывает более высокий приоритет, чем 1-я задача. Я надеюсь, что это очистит.

Хотя некоторые дополнительные наблюдения помогут вам лучше понять, что, если вы задали приоритеты как 7,8 и 9. Так как это очередь переднего плана, 7 будет выполняться сначала, а затем 7, а затем 8. Один на один и исключительно в этом порядке, а пока 7 выполняется, 8 и 9 будут ждать, то есть очередь Foreground будет выполняться синхронно друг с другом.

Но Background и Idle очередь не будет вести себя таким образом, когда выполнение асинхронно с другими задачами и задачами будет следовать приоритету. И сначала Background, и Idle очередь.

Надеемся, что это объяснение в какой-то мере поясняется.

Ответ 2

Это связано с тем, что первый MessageBox блокирует поток UI.

Что Dispatcher.BeginInvoke() делает под капотом, принимает ваш делегат и планирует его запустить в основном потоке пользовательского интерфейса во время следующего периода ожидания. Однако MessageBox будет блокировать любую нить, из которой она вызывается, до ее закрытия. Это означает, что второй MessageBox не может отображаться до тех пор, пока первый не будет очищен, так как планировщик потоков пользовательского интерфейса видит, что поток уже используется (ожидание первого MessageBox будет очищено) и не может выполнить следующий делегат, содержащий второй MessageBox.