Улучшение производительности WPF путем разбивки пользовательского интерфейса на "регионы" - возможно ли это?

Я провел очень простой тест производительности в клиентском приложении WPF:

public partial class MainWindow : Window
{
    private ObservableCollection<int> data = new ObservableCollection<int>();
    public ObservableCollection<int> DataObj { get { return data; } }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        for (int j = 0; j < 5; j++)
        {
            Thread t = new Thread(() =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        Thread.Sleep(5);
                        Dispatcher.Invoke(new Action(() => { data.Add(1); })); //updates the count
                        Dispatcher.Invoke(new Action(() => { richTextBox1.AppendText("1"); })); //updates the string data
                    }
                });

            t.Start();
        }
    } 

Тогда у меня есть два элемента управления в пользовательском интерфейсе: a TextBlock и a RichTextBox.

TextBlock привязан к свойству Count источника данных, а RichTextBox добавляет каждое новое значение данных в свою текстовую строку (т.е. отображает содержимое данных).

Если я отключу привязку RichTextBox, TextBlock обновляется очень быстро, циклически переходя по счету. Однако включение привязки RichTextBox замедляет все, оба элемента управления обновляются в "глобусах", возможно, один или два раза в секунду. В других словах весь пользовательский интерфейс работает в темпе привязки RichTextBox.

Есть ли способ нарушить эту зависимость производительности? Я понимаю, что RichTextBox может быть медленным, но почему он должен замедлять ускоряющий быстрый TextBlock?

Ответ 1

Специфика WPF заключается в том, что в окне есть только один поток пользовательского интерфейса.

Хотя можно использовать другое окно и заставить его выглядеть так, как будто он является частью текущего приложения (установите для свойства WindowStyle значение None и обновите позицию и размер), это выглядит неестественно, и есть лучший способ устраняйте проблемы с производительностью.

Как известно, для обновления пользовательского интерфейса из фонового потока необходимо использовать класс Dispatcher. Метод BeginInvoke имеет необязательный параметр типа DispatcherPriority, который имеет следующие значения.

  • SystemIdle
  • ApplicationIdle
  • ContextIdle
  • Фон
  • Ввод
  • Loaded
  • Render
  • DataBind
  • Normal
  • Отправить

Значение по умолчанию Normal (9), это почти самый высокий приоритет и применяется неявно, когда вы вызываете метод BeginInvoke без параметров. Вызов RichTextBox в вашем примере имеет этот приоритет.

Но ваш TextBlock, связанный с этим свойством и не обновляемый вручную, имеет более низкий приоритет DataBind (8), поэтому он обновляется медленнее.

Чтобы сделать привязку быстрее, вы можете уменьшить приоритет вызова до RichTextBox и установить значение ниже 8, например Render (7).

Dispatcher.Invoke(/*...*/, DispatcherPriority.Render);

Это поможет с привязкой, но пользовательский интерфейс не будет реагировать на щелчки мыши, вы не сможете даже закрыть окно.

Продолжайте уменьшать приоритет:

Dispatcher.Invoke(/*...*/, DispatcherPriority.Input);

Приложение реагирует лучше, но по-прежнему невозможно ввести что-то в RichTextBox, пока оно заполнено текстом.

Поэтому конечное значение Background (4):

Dispatcher.Invoke(new Action(() => { richTextBox1.AppendText("1"); }),
                  DispatcherPriority.Background);