Monthly Archives: April 2012

WinRT vs WPF Drawing Performance

Although WPF/XAML are great for developing user interfaces it does have a reputation for being slow at drawing. Certainly it is recommended you keep the visual tree down to less than 10,000 objects if you want anything like a responsive user experience.

So how does the new WinRT compare to WPF? There is no need to wonder when we can create a couple of simple tests and see how it works in practice. My test consists of a single XAML page that contains a TextBlock and Grid. The TextBlock will be used to indicate how many frames per second are being drawn and the Grid used to host instances of the Rectangle shape class. The WPF version of the XAML is as follows.

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="1*"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="speed"/>
            <Grid x:Name="g" Grid.Row="1"/>
        </Grid>
    </Window>

The code behind is a little more verbose but simple enough. It creates rows and columns for the Grid and fills each cell with a Rectangle instance. It then kicks off a timer that fires as quickly as possible. The timer tick will update the Brush instances used to color the background and border of each Rectangle. By counting how many times per second the timer ticks we can see the update rate.

    public partial class MainWindow : Window
    {
        public int _count = 0;
        public Random _rand = new Random();
        public DateTime _startTime;
        public DispatcherTimer _timer = new DispatcherTimer();
        public List _shapes = new List();
        public List _brushes = new List();

        public MainWindow()
        {
            InitializeComponent();

            int bounds = 40;
            for (int i = 0; i &lt; bounds; i++)
            {
                g.RowDefinitions.Add(new RowDefinition() 
                    { Height = new GridLength(1, GridUnitType.Star) });

                g.ColumnDefinitions.Add(new ColumnDefinition 
                    { Width = new GridLength(1, GridUnitType.Star) });
            }

            for (int x = 0; x &lt; bounds; x++)
            {
                for (int y = 0; y  1000)
            {
                if (_count &gt; 0)
                    speed.Text = _count.ToString();
                else
                    speed.Text = "0";

                _count = 0;
                _startTime = DateTime.Now;
            }

            int index = _rand.Next(_brushes.Count);
            foreach (Rectangle tb in _shapes)
            {
                tb.Fill = _brushes[index++ % _brushes.Count];
                tb.Stroke = _brushes[index++ % _brushes.Count];
            }

            _count++;
        }
    }

Running on a Windows 7 machine we getting the following output…

WPF Rectangle Drawing

…which averages around the 15 frames per-second. It gives the same frame rate when run maximized or restored. Running the same test as a Metro app at full screen we get the following…

WinRT Rectangle Drawing

…which is 40 frames compared to the 15 for WPF. A nice 260% performance improvement. But applications are made up of more than simple rectangles. So with a minor change we can fill each cell with a TextBlock that has a new Text value set for each timer tick. Running this under WPF we get…

WPF Text Drawing Performance

…around 11 frames per second and the Metro app version comes out at…

WinRT Text Drawing Performance

…around 10 frames on average, the screenshot happened to be taken when it was at 9 but it averaged 10. So it seems that Text drawing performance is about the same and rectangle drawing is significantly faster under WinRT. Obviously this is only a rough and ready comparison but it is very positive to see that even at the Consumer Preview stage the WinRT implementation is at least at good and in many areas better than WPF. You have to assume that it will only get faster in the future as developer time shifts from adding features to improving performance.

WinForms: Cross Thread Calls

.NET Framework 1.0 / 1.1

In the beginning there was only WinForms for developing user interfaces on the desktop. As WinForms is just a wrapper layer on top of Win32 it means your .NET controls have thread affinity just like Win32 controls. So any method or property you call on a control must occur on the same thread that created the control.

Long running tasks are the bane of user interfaces. If you perform them synchronously they freeze the application and make it unresponsive until the task is completed. To avoid this we use worker threads. But with thread affinity we cannot update the user interface from the worker thread when the task has some feedback to show. Fortunately the .NET designers thought of this and provided the Control.InvokeRequired and Control.Invoke methods.

To demonstrate this we have a simple WinForms application. It consists of a Form with a button for starting a long running task and a progress bar for showing the progress of that task as it executes. The button1_Click event handler creates a thread pool work item in order to kick off the LongProcess1 method in a separate worker thread. We simulate a long running operation by placing a Thread.Sleep inside a loop and repeatedly call the ProgressUpdate1 method to update the progress bar with current state of the task.

    private void button1_Click(object sender, EventArgs e)
    {
        Console.WriteLine("button_Click Thread:{0}",
            Thread.CurrentThread.ManagedThreadId);

        ThreadPool.QueueUserWorkItem(new WaitCallback(LongProcess1), this);
    }

    private void LongProcess1(object state)
    {
        Console.WriteLine("LongProcess Thread:{0}",
            Thread.CurrentThread.ManagedThreadId);

        Form1 form = (Form1)state;

        for (int i = 0; i < 100; i+=20)
        {
            Thread.Sleep(100);
            form.ProgressUpdate1(i);
        }
    }

    private void ProgressUpdate1(int percent)
    {
        Console.WriteLine("ProgressUpdate Thread:{0} Percent:{1}",
            Thread.CurrentThread.ManagedThreadId, percent);

        if (progressBar1.InvokeRequired)
            progressBar1.BeginInvoke(new UpdateDelegate(ProgressUpdate1),
                                     new object[] {percent});
        else
            progressBar1.Value = percent;
    }

    private delegate void UpdateDelegate(int percent);

We can see inside the ProgressUpdate1 the use of the InvokeRequired property to discover if the current thread is the same as the thread the control was created on. If it returns True then you need to use either the BeginInvoke or Invoke method to request the provided delegate be executed on the thread that created the associated control.

Internally the BeginInvoke is implemented by posting a custom windows message to a hidden control that exists on the user interface thread. Win32 knows the thread associated with each control and so the message is automatically queued to the message queue of the owning thread. Once the message is dispatched the hidden controls WndProc will process it by calling the provided delegate.

Running our Form and clicking the Start button gives this…

WinForms Cross Thread Calls

…with the following console output…

WinForms Invoke/InvokeRequired

Note the area in yellow highlight that shows each update results in the ProgressUpdate1 method being called twice. The first time it is called on thread 10, the worker thread. This causes the BeginInvoke to be called and when the delegate is called be are back on thread 9, the user interface thread.

Although this technique works it has some disadvantages:-

  • The Form is passed into the worker
  • Which callback method to call is hardcoded
  • The callback method is called twice instead of once
  • InvokeRequired/BeginInvoke pattern must be used in all callback methods


.NET Framework 2.0

Most of the above problems can be solved by making use of the SynchronizationContext class that was introduced in version 2.0 of the .NET Framework. The SynchronizationContext is a base class that is used to abstract away the details of how to execute code in the correct context, in practice making sure code is executed in the correct thread.

In our case we are interested in the derived class called WinFormsSynchronizationContext. An instance of this context is created the first time any WinForms control is created within a thread and it is assigned to the thread local SynchronizationContext.Current. So although you will not see that class explicitly created if is the one in existence. You can check this yourself by stepping through the new version of the code and examining the type of the SynchronizationContext.Current value.

        private void button2_Click(object sender, EventArgs e)
        {
            Console.WriteLine("button_Click Thread:{0}",
                Thread.CurrentThread.ManagedThreadId);

            ThreadPool.QueueUserWorkItem(new WaitCallback(LongProcess2),
                                         SynchronizationContext.Current);
        }

        private void LongProcess2(object state)
        {
            Console.WriteLine("LongProcess Thread:{0}",
                Thread.CurrentThread.ManagedThreadId);

            SynchronizationContext context = (SynchronizationContext)state;

            for (int i = 0; i < 100; i+=20)
            {
                Thread.Sleep(100);
                context.Post(new SendOrPostCallback(ProgressUpdate2), i);
            }
        }

        private void ProgressUpdate2(object percent)
        {
            Console.WriteLine("ProgressUpdate Thread:{0} Percent:{1}",
                Thread.CurrentThread.ManagedThreadId, percent);

            progressBar1.Value = (int)percent;
        }

Using the Post method of the context ensures the passed in delegate is executed back on the original WinForms thread. We no longer get two calls to the callback because the callback is only ever called when already back on the correct thread. We can see this is the case in console output.

WinForms SynchronizationContext

We still have one of our disadvantages to take care of. The actual callback method is still hardcoded into the worker method. It would be better if the callback were passed into the long running method as a parameter instead of being fixed. This allows the long running code to be reused from multiple points in our WinForms application where each usage needs a different callback used.

Our updated code now looks like the following with a helper class used to pass two pieces of information into the worker method.

        public class ItemContext : Tuple>
        {
            public ItemContext(SynchronizationContext context, Action action)
                : base(context, action)
            { }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(
                new WaitCallback(LongProcess3),
                new ItemContext(SynchronizationContext.Current,
                                new Action((p) => progressBar1.Value = p)));
        }

        private void LongProcess3(object state)
        {
            ItemContext context = (ItemContext)state;

            for (int i = 0; i < 100; i+=20)
            {
                Thread.Sleep(100);
                context.Item2(innerI)), null);
            }
        }

We have achieved our goal of using another thread for executing an operation whilst safely returning to the WinForms thread for processing updates or results from the operation. We have also parameterized the operation with a SynchronizationContext and callback so the operation can be reused.

.NET Framework 4.5

With the introduction of the async and await keywords we can improve our code again. Instead of using the ThreadPool explicitly we can use the static Task.Run method to request an operation be executed asynchronously on another thread. This has the advantage that we can await the operation,  so the button4_Click method will continue on the original WinForms thread once the operation has completed.

So performing post-operation code becomes trivial as we just add it immediately after the await. This is easier than passing a callback into the operation. Here is an example of how we could do this without the need for a helper class as seen before.

        private async void button4_Click(object sender, EventArgs e)
        {
            var context = SynchronizationContext.Current;
            await Task.Run(() => LongProcess4(
                               (percent) => context.Post(
                                    new SendOrPostCallback(
                                        (_) => progressBar.Value = percent), null)));

            // Post-Operation code goes here...
        }

        private void LongProcess4(Action progress)
        {
            for (int i = 0; i < 100; i+=20)
            {
                Thread.Sleep(100);
                progress(i);
            }
        }

Our final version of code will use a helper method that makes it simple to solve our original problem, how to execute code on another thread and then get back to the original WinForms thread with feedback.

        private async void button_Click(object sender, EventArgs e)
        {
            await RunWithCallback(LongProcess4,
                                  (percent) => progressBar.Value = percent);
        }

        private Task RunWithCallback(Action action, Action callback)
        {
            var context = SynchronizationContext.Current;
            return Task.Run(() => action(
                                (p) => { context.Post(new SendOrPostCallback(
                                    (_) => callback(p)), null); }));
        }

You can call RunWithCallback with two parameters, a delegate to execute as the operation on the background thread and another delegate which is the callback method to be passed into the operation. With the use of lambdas it makes for very concise code for methods that would have been trivial.