Creating WPF window's on dedicated threads

Creating Multi Threaded WPF Applications

There can be certain scenario's when you're application has to perform some heavy CPU intensive tasks and is running on UI thread (your WPF application) could drastically pull down the performance of your application as UI related tasks in WPF could themselves be extensive, as WPF forces all the UI work being done on the thread that created the UI . And for a UI intensive application (for example, which shows a real time streaming of data) with high resource consumption, the cost of rendering the visual could prove too much for the single thread. This in turn could lead to freezing of your UI.

If your application reaches to such a state it is always a better idea to create your resource intensive UI applications on dedicated threads, this article will give you an incremental approach to creating such an Application.

Single Threaded Application in WPF:

First create a WPF application in Visual Studio. Choose File->New->Project, then Visual C# -> WPF Application. Give appropriate name to the application and click "Ok"

Add the following piece of code to window1.xaml.cs:

using System.Windows;
using System.Threading;

namespace SingleThreadUI
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Thread _thread = Thread.CurrentThread;
            this.DataContext = new { ThreadID = _thread.ManagedThreadId };
        }

        private void OnCreateNewWindow(object sender, RoutedEventArgs e)
        {
            Window1 w1 = new Window1();
            w1.Show();
        }
    }
}

In the constructor of the window1 class we create the data context containing thread's ID which could be used to bind to a TextBox or Label in the UI. A click event handler is also there which is used to create new Windows. The Window.xaml should look like this:

<Window x:Class="SingleThreadUI.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <StackPanel Orientation="Horizontal" Height="30">
            <TextBlock Text="Window created on :" FontSize="13"/>
            <TextBlock Text="{Binding ThreadID}" FontSize="13"/>
        </StackPanel>      
       
<Button Click="OnCreateNewWindow" Content="New Window" Width="100" Margin="10" Height="25"/>
    </StackPanel>
</
Window>

When we click on the New Window button, it will create clones of the window and one could see the ThreadId remains same for all the windows.

image1.gif

Here we are still creating the Window clones, but we wrapped the window creation code in Click event Handler of the New Window button.

Code for creating dedicated UI threads for each Window:

For creating WPF window on dedicated threads, Change the OnCreateNewWindow event Handler as:

private void OnCreateNewWindow(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(() =>
            {

                Window1 w = new Window1();
                w.Show();
               
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }

Here the window creation code is exactly the same, except that we wrapped it in a thread delegate.

If you notice that we have to explicitly set new thread's apartment state to STA, this is WPF requirement.

Again, if you now click the "New Window", you could see that the application would crash, the reason is that our newly created thread is not enabled to support WPF window infrastructure i.e. it does not provide support for Windows pumping.

To fix the above problem,

WPF windows like other Windows rely on Windows message pump. In WPF we could do this by making use of Dispatcher class. For the main UI application WPF would take care of starting the dispatcher threads but for our own private UI thread, we have to explicitly start them. Lets see how we could do so:

private void OnCreateNewWindow(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(() =>
            {
 
                Window1 w = new Window1();
                w.Show();
                 
                System.Windows.Threading.Dispatcher.Run(); 

            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }

This piece of code starts the message pump on it, now if you click the New Window button, you will get the fully functional window on a new thread.

There is one more issue to be solved; closing a particular window does not terminate this window's thread dispatcher, so the thread keeps running even after closing the window, the process will not close. The solution to this is that mark the thread as background thread. The proper implementations will gracefully shutdown the dispatcher when no longer needed.

private void OnCreateNewWindow(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                Window1 w = new Window1();
                w.Show();
                 w.Closed += (sender1, e1) => w.Dispatcher.InvokeShutdown();

                System.Windows.Threading.Dispatcher.Run();

            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
        }

The highlighted code shows the graceful exit of the window whenever it is closed and we have marked each thread to be run as a backgroubd thread.

The code above completes the task of shutting down the dispatcher for window's UI thread.