Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 the 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?

like image 669
flesh Avatar asked Apr 09 '11 15:04

flesh


1 Answers

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.

  1. SystemIdle
  2. ApplicationIdle
  3. ContextIdle
  4. Background
  5. Input
  6. Loaded
  7. Render
  8. DataBind
  9. Normal
  10. 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);
like image 66
vortexwolf Avatar answered Oct 19 '22 16:10

vortexwolf