#c# #wpf #performance
#c# #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 вполне может быть медленным, но почему он должен замедлять работу текстового блока, который в противном случае быстро сокращается?
Комментарии:
1. Привет, тестовый пример производительности. Похоже, что это еще не вымерший вид.
2. Что произойдет, если отключить привязку TextBlock?
3. RichTextBox по-прежнему обновляется со скоростью около 2 кадров в секунду. Я думаю, мне нужно несколько потоков диспетчера, но я не нашел никакого способа создать единый пользовательский интерфейс из нескольких панелей / окон.
4. Я знаю обходной путь:
Dispatcher.Invoke(new Action(() => { richTextBox1.AppendText("1"); }), DispatcherPriority.Background);
. Я не совсем понял проблему, но этот код выполняется быстрее, чем вызов с обычным приоритетом.5. vorrtex — это хорошее начало. определенное улучшение, но это действительно похоже на обходной путь. Что нам действительно нужно, так это способ определения элементов в дереве, которые имеют свои собственные
Dispatcher
потоки, по сути, многопоточный пользовательский интерфейс. Я нашел похожий код для отображения видео в пользовательском интерфейсе, но ничего, что касалось бы проблемы, описанной выше..
Ответ №1:
Специфика WPF заключается в том, что на каждое окно приходится только один поток пользовательского интерфейса.
Хотя можно использовать другое окно и сделать так, чтобы оно выглядело так, как будто оно является частью текущего приложения (установите для свойства WindowStyle значение None и измените положение и размер), это выглядит неестественно, и есть лучший способ устранить проблемы с производительностью.
Как известно, необходимо использовать Dispatcher
класс для обновления пользовательского интерфейса из фонового потока. BeginInvoke
Метод имеет необязательный параметр типа DispatcherPriority, который имеет следующие значения.
- SystemIdle
- ApplicationIdle
- ContextIdle
- Предыстория
- Ввод
- Загружено
- Визуализация
- Привязка данных
- Нормальный
- Отправить
Значение по умолчанию равно 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);
Комментарии:
1. Спасибо. Вам не обязательно использовать
BeginInvoke
, чтобы увидеть улучшение (фактически, когда вы выполняете два обновления, они полностью не синхронизированы друг с другом — текстовый блок завершается задолго доRichTextBox
). Я предполагаю, что диспетчер не может обрабатывать элементы в своей очереди достаточно быстро при использованииInvoke
разрешения добавления элемента с более высоким приоритетом, прежде чем он сможет обработать обновление доRichTextBox
.2. Кроме того, я не совсем понимаю, зачем нам нужен,
DispatcherPriority.Background
а не просто какой-то приоритет, который ниже, чем уTextBlock
обновления? Конечно, все, что имеет значение, — это их относительные приоритеты? Или в очереди диспетчера есть другие события, которые я не принимаю во внимание?3. @flesh я использую
BeginInvoke
потому что этот вызов не ожидает завершения операции.Background
Приоритет вызывает наименьшее количество проблем. Как я уже писал в ответе, вы можете использоватьRender
приоритет, который на одно число меньшеDataBinding
, но если вы попытаетесь щелкнуть где-нибудь, это не сработает, потому что события мыши имеют более низкий приоритет. Вы можете попробовать установитьInput
приоритет, а затем ввести что-то вRichTextBox
, пока оно заполняется текстом. После этого вернитесь кBackground
приоритету и попробуйте ввести что-нибудь еще раз. Вы увидите разницу.