Running WPF Application with Multiple UI Threads


Introduction

It’s a good, time-proven practice to perform long, CPU intensive tasks on some sort of a background thread to improve your UI thread responsiveness. Sometimes though UI-related tasks themselves can be quite expensive. WPF, for examples, forces you to do all UI work on the thread that created the UI. A very flexible WPF measure/layout paradigm for UI rendering also comes with high CPU usage cost. In a very UI intensive application (for example, trading app with about ten windows showing real-time montage and blotter data) simply the cost of generating and laying out visuals can become too high for a single thread to keep up. When your UI thread saturates individual windows may start skip rendering cycles, become slow to response to user input, or even freeze. If your UI thread approaches this kind of saturation you should consider creating dedicated UI threads for some (or all) of your UI-intensive windows. This post is a step by step walk-through of doing just that.

Creating single-threaded WPF UI application

To start lets create a basic WPF application we can work with. In Visual Studio go File/New/Project, then Visual C#, Windows, WPF Application. Name the project “WpfThreadLab” and click “OK”. The automatically generated Window1.xaml window doesn’t display anything by default, so let add something useful for out lab. We will display the ID for the thread that owns the window. First open Window1.xaml.cs and add the following code”:

using System.Threading;
using System.Windows;

namespace WpfThreadLab
{
 /// <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 w = new Window1();
    w.Show();
  }

 }
}

Constructor creates data context containing thread’s ID that we can data bind to. We also add a click event handler that we will use to create new windows on demand. This is how corresponding Window1.xaml may look like:

<Window
    x:Class="WpfThreadLab.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="100" Width="200"
>
 <StackPanel>
  <StackPanel Orientation="Horizontal">
   <TextBlock Text="Thread's ID is "/>
   <TextBlock Text="{Binding ThreadId}"/>
  </StackPanel>
  <Button
   Click="OnCreateNewWindow"
   Content="Create new Window1"
  />
 </StackPanel>
</Window>

If you run the application now you should see the following window:

image

Clicking on the button will create new clones of the window. You will see that they all belong to default UI thread with managed ID 1.

Adding code to create dedicated UI threads for each window

The quick and dirty solution may include the following modifications to our OnCreateNewWindow handler:

private void OnCreateNewWindow(
 object sender,
 RoutedEventArgs e)
{
 Thread thread = new Thread(() =>
  {
   Window1 w = new Window1();
   w.Show();
  });

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

The window creation code is exactly the same, except that we wrapped it in a thread delegate. This delegate is passed to a newly created thread that is started at the end of the handler. Note that we explicitly setting new thread’s apartment state to STA, this is a WPF requirement.

If you run the application now and click on the button, you will notice that a new window appears momentarily and then dies. The reason is that our newly created thread is not enabled to support WPF window infrastructure. In particular, it provides no support for Windows message pumping, and this is something we will fill in next.

Making a thread “UI thread”

WPF window like any other window relies on Windows message pump. In WPF word this functionality is provided by a class called Dispatcher. WPF Application object takes care of starting dispatcher for the main UI thread for us, but we have to explicitly start it for our own private UI threads. The easiest way to do it is to call static method Run() on the Dispatcher class:

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();
}

The code in bold shows the modification. This blocks the thread and starts a message pump on it. If you run application now and start clicking the button you will notice that fully functional window is created every time you click, and every time on a new thread.

There is one catch to our simplified solution. Closing a particular window does NOT terminate this window’s thread dispatcher, so the thread keeps running and, after closing all windows, the process will not terminate and will become a ghost process. Simple (and not correct) solution to this is to mark our threads as background (using thread.IsBackground = true;). This will force them to terminate when main UI thread terminates. The proper implementation will gracefully shut down the dispatcher when it is no longer needed. The code below is an example of a strategy to terminate the dispatcher when window closes:

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

     w.Closed += (sender2, e2) =>
      w.Dispatcher.InvokeShutdown();

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

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

The code above in bold accomplishes the task of shutting down the dispatcher for window’s UI thread.

Conclusion

Running multiple UI threads in WPF application is actually rather trivial. In the nutshell it boils down to creating your dedicated thread and creating your windows inside this thread’s proc. Remember to conclude your UI thread with a call to WPF dispatcher. Also, decide on your “exit” strategy (often, shutting down dispatcher in response to closing the primary window for this thread).

About these ads
28 comments
  1. Yogesh said:

    Hello Eugene,

    Thanks a lot. This article solved a problem I was facing for some time. Great article. :)

    Regards,
    Yogesh.

  2. Vinayak said:

    Hey Eugene,
    This helps only if you want to run two unrelated windows on different threads. What If I want to make a window running on one thread modal to a window running on another one. Do you have a solution to that? I dont have time to use interop services :( . help would be greatly appreciated.
    Thanks in advance,
    Vinayak

  3. Kishor Aher said:

    Hi Eugnene,
    Very informative article. I am facing similar problem but with respect to various control in the same window.

    Kishor Aher.

  4. Philippe Monteil said:

    _very_ informative article: thanks a lot!

    Philippe

  5. kumar said:

    Hey Eugene,

    thanks for the article it solved my problem partially. The remaining problem is with the dependency property. Earlier I use to have multiple windows on the same thread sharing the same property. Being a same thread the property has no problem in using different windows. Now I am getting exceptions for not being owner of the dependency property while accessing from different threads. I am working on it but wondering you might have come across the situation in the past and have a solution to suggest to me.

    thanks alot
    Kumar.

  6. Looooooka said:

    kumar…google up multi threading and invoking.
    the same solution that worked for .net forms applies to WCF forms.

  7. Ali Sali said:

    Do you have any idea how to do that with controls?
    Like create a button in one thread and then add that button to the window which lives in another thread?
    Is this possible at all?

  8. Boris said:

    Is there a way to set ownership relation between the parent window and the one created in separated thread? I.e. to set Owner of the 2nd window to point to the main window?

    • andrew wang said:

      test on .net 3.5 OK, not test .net 4.0

      typeof(System.Windows.Window).InvokeMember(“_ownerHandle”, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, WPFWindowAnotherThread, new object[] { WindowOwnerWin32Handle });

  9. Alexander said:

    Nice Article!

  10. Ken said:

    Very nice.

    Is it possible to use this with UserControls? So my UserControl inside a WPF window will run on its own UI thread?

  11. Bobby said:

    I think there is an error here:
    w.Closed += (sender2, e2) =>
    w.Dispatcher.InvokeShutdown();

    Should be:
    this.Closed += (sender2, e2) =>
    w.Dispatcher.InvokeShutdown();

    isn’t it?

    • Nestiiii said:

      No there is no error. The Dispatcher for the “sub window” should be stopped if the “sub window” closes. What you suggest is the same to setting the Thread.IsBackground property (When the main window closes stop thread)

  12. Louis said:

    I don’t know if other people have noticed the same thing but executing the code:

    w.Closed += (sender2, e2) => w.Dispatcher.InvokeShutdown();

    causes a RaceOnRCWCleanup MDA assistant to pop while the debugger is attached. I have ignored this so far but I wonder, am I ignoring this at my peril? I would rather have the thread cleanup than have clean debug sessions.

  13. Andrel said:

    Ty a lot, was looking for this solution the whole morning!

    You the man !

  14. Thanks Eugene for the nice article. I did similar UI threading in Windows Forms and it took me a week to figure out how to shut down the message queue for the second thread so that as ghost exe would not be left over.
    I found this article from searching how to set the Owner property on the second thread’s window so that the window will come up centered and stay on top of the owner. Thanks to Andrew Wang, I have the solution.
    Here it is an example of using it:

    public class LoginClass
    {

    private UserLogin login;

    ///
    /// Callback method from Close event of login window
    ///
    ///
    public void HandleLoginExit(bool DialogResult)
    {
    if (DialogResult == true)
    {
    Window1.MainPageRef.Dispatcher.Invoke(new LoginDialogClosed(AssignUA), null);
    }
    else
    {
    Window1.MainPageRef.Dispatcher.Invoke(new LoginDialogClosed(ReportError), null);
    }
    }

    public void Login()
    {
    //Login here
    login = new UserLogin(this);

    // do login.Owner = Window1.Window1Ref the hard way since you can’t access Window1.Window1Ref on this thread
    //get the win32 handle to parent
    string strFriendlyName = AppDomain.CurrentDomain.FriendlyName;
    Process[] pro = Process.GetProcessesByName(
    strFriendlyName.Substring(0, strFriendlyName.LastIndexOf(‘.’)));

    //Andrew Wang’s reply for this answer on how to set the owner
    typeof(System.Windows.Window).InvokeMember(“_ownerHandle”,
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField,
    null, login, new object[] { pro[0].MainWindowHandle });

    login.WindowStartupLocation = WindowStartupLocation.CenterOwner; //center the menu on the owner

    //force Dispatcher for this thread to gracefully shut down when form closes
    login.Closed += (sender, e) => login.Dispatcher.InvokeShutdown();
    //Dispatcher.Run(); //attach this UI thread to the Dispatcher event queue
    // I don’t see why Dispatcher.Run() is needed for ShowDialog
    // seems to work without it
    login.ShowDialog();
    }
    }
    //Note: AssignUA is a LoginClass method for setting up User Authorization depending on the logged in user’s defined roles.

  15. thanks for the nice article! it helped me with a particular issue i was trying to investigate.
    i’m not really a WPF dev so i got lost why a wpf app from a colleague of mine was not working properly.

    the app creates a thread that uses an mfc dll which internally creates a win32 window.
    however, the window never receives any window messages (sent internally).
    it turns out that i needed to make the thread to be a UI thread, so window messaging is supported.

    thanks again for the nice article!

  16. skiptpawa said:

    Does annyone know how to invoke a method that is in the second window from the first window?

    • icehuli said:

      You can pass the Dispatcher of the first window(thread) to the second window as a variable, then you can invoke a method form the first window
      firstWindow.BeginInvoke(DispatcherPriority.Input, (Action)(
      () =>
      {
      //do something in mainWindow thread
      }));

  17. ice Huli said:

    I went into an exception caused by Application.LoadComponent, since it is not thread safe:
    Unhandled Exception: 40028664System.ArgumentException: An entry with the same ke
    y already exists.
    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
    at System.Collections.Generic.SortedList`2.Add(TKey key, TValue value)
    at System.IO.Packaging.Package.AddIfNoPrefixCollisionDetected(ValidatedPartUr
    i partUri, PackagePart part)
    at System.IO.Packaging.Package.GetPartHelper(Uri partUri)
    at System.IO.Packaging.Package.GetPart(Uri partUri)
    at System.Windows.Application.GetResourceOrContentPart(Uri uri)
    at System.Windows.Application.LoadComponent(Object component, Uri resourceLoc
    ator)

    so I add a lock around the creation of new Window1(), the problem seems solved.

    private Window1 _w = null;
    private static Object windowLock = new Object();

    private void OnCreateNewWindow(
    object sender,
    RoutedEventArgs e)
    {
    Thread thread = new Thread(() =>
    {
    lock (windowLock)
    {
    _w = new Window1();
    }
    _w.Show();

    _w.Closed += (sender2, e2) =>
    _w.Dispatcher.InvokeShutdown();

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

    int maxWaitTime = 200;
    int waited = 0;
    while (_w == null && waited++
    {
    _w.Hide();
    }), System.Windows.Threading.DispatcherPriority.ContextIdle, null);
    }

  18. ice Huli said:

    I went into an exception caused by Application.LoadComponent, since it is not thread safe:
    Unhandled Exception: 40028664System.ArgumentException: An entry with the same ke
    y already exists.
    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)

    at System.Windows.Application.LoadComponent(Object component, Uri resourceLoc
    ator)

    so I add a lock around the creation of new Window1(), the problem seems solved.

    private Window1 _w = null;
    private static Object windowLock = new Object();

    private void OnCreateNewWindow(
    object sender,
    RoutedEventArgs e)
    {
    Thread thread = new Thread(() =>
    {
    lock (windowLock)
    {
    _w = new Window1();
    }
    _w.Show();

    _w.Closed += (sender2, e2) =>
    _w.Dispatcher.InvokeShutdown();

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

    int maxWaitTime = 200;
    int waited = 0;
    while (_w == null && waited++
    {
    _w.Hide();
    }), System.Windows.Threading.DispatcherPriority.ContextIdle, null);
    }

  19. ivanprytsko said:

    Very clear and great article. Thank you ;)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: