Improving WPF performance by breaking up the UI into 'regions' - is this possible?
I've run a very simple performance test on a WPF client app:
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();
}
}
I then have two controls in the UI: a TextBlock
and a RichTextBox
.
The TextBlock
is bound to the Count
property of the datasource, whilst the RichTextBox
appends each new data value to its text string (ie. displays the content of the data).
If I disable the RichTextBox
binding, the TextBlock
updates very quickly, cycling through the count. However, enabling th开发者_开发技巧e RichTextBox
binding slows everything down, both controls update in "globs", maybe once or twice per second. In otherwords the entire UI runs at the pace of the RichTextBox
binding.
Is there a way to break this performance dependency? I understand the RichTextBox may well be slow, but why does it have to slow down the otherwise lightening fast TextBlock?
The specific of WPF is that there is only one UI thread per window.
Although it is possible to use other window and make it look as if it is part of the current application (set the WindowStyle property to None and update position and size), it doesn't look natural and there is better way to sort out performance issues.
As is known, it is necessary to use the Dispatcher
class to update the UI from a background thread. The BeginInvoke
method has the optional parameter of the DispatcherPriority type which have the following values.
- SystemIdle
- ApplicationIdle
- ContextIdle
- Background
- Input
- Loaded
- Render
- DataBind
- Normal
- Send
The default value is Normal (9)
, it is almost the highest priority and it is implicitly applied whenever you call the BeginInvoke
method without parameters. The call to the RichTextBox
in your example has this priority.
But your TextBlock
which is bound to the property and isn't updated manually, has the lower priority DataBind (8)
, that's why it is updated slower.
To make binding quicker, you can reduce the priority of the call to the RichTextBox
and set a value lower than 8, for example Render (7)
.
Dispatcher.Invoke(/*...*/, DispatcherPriority.Render);
It will help with the binding, but the UI will not respond on mouse clicks, you will not be able even to close the window.
Continue to reduce the priority:
Dispatcher.Invoke(/*...*/, DispatcherPriority.Input);
The application responds better, but it is still impossible to type something in the RichTextBox
while it is populated by text.
Therefore the final value is Background (4)
:
Dispatcher.Invoke(new Action(() => { richTextBox1.AppendText("1"); }),
DispatcherPriority.Background);
精彩评论