Can/Does WPF have multiple GUI threads? Or does it always only have one GUI thread (even if I have multiple windows/dialogs)?
I'm asking because I have events coming from other threads and I'd like to handle them in the GUI thread (because I need to modify the controls of my main window accordings to the events).
Btw: I know I need to use a Dispatcher
object for this purpose. So, I could rephrase my question and ask: Is there always only one Dispatcher
object for all GUI elements in WPF?
WPF supports a single-threaded apartment model that has the following rules: One thread runs in the entire application and owns all the WPF objects. WPF elements have thread affinity, in other words other threads can't interact with each other.
WPF applications start their lives with two threads: one for rendering and another for the UI. The rendering thread runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code. The UI thread queues work items inside an object called a Dispatcher.
Typically your WPF projects have a single Dispatcher object (and therefore a single UI thread) that all user interface work is channeled through.
The UI thread queues work items inside an object called a Dispatcher. The Dispatcher selects work items on a priority basis and runs each one to completion. Every UI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread.
Based on the link in the first answer I did some verification on my own. I'd like to share the results here. First of all:
There can be multiple GUI threads (and therefor multiple Dispatcher
instances).
However:
Simply creating a new window (modal or not) does not create a new GUI thread. One needs to create the thread explicitly (by creating a new instance of Thread
).
Note: Instead of using separate threads, modal dialogs are likely being realized by using Dispatcher.PushFrame() which blocks the caller of this method while still allowing events to be dispatched.
I've created a simple WPF class (again, based on the link from the first answer) to verify all this stuff. I share it here so you can play around with it a little bit.
MainWindow.xaml:
<Window x:Class="WindowThreadingTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="250" Height="130"> <StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Thread's ID is "/> <TextBlock x:Name="m_threadId"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Thread's threading apartment is "/> <TextBlock x:Name="m_threadTA"/> </StackPanel> <Button Click="OnCreateNewWindow" Content="Open New Window"/> <Button Click="OnAccessTest" Content="Access Test"/> </StackPanel> </Window>
MainWindow.xaml.cs:
using System; using System.Threading; using System.Windows; using System.Windows.Media; namespace WindowThreadingTest { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private static uint s_windowNumber = 0; private readonly MainWindow m_prevWindow; public MainWindow() : this(null) { } public MainWindow(MainWindow prevWindow) { InitializeComponent(); this.m_prevWindow = prevWindow; this.Title = String.Format("Window {0}", ++s_windowNumber); Thread thread = Thread.CurrentThread; this.m_threadId.Text = thread.ManagedThreadId.ToString(); this.m_threadTA.Text = thread.GetApartmentState().ToString(); } private void OnCreateNewWindow(object sender, RoutedEventArgs e) { CreateNewWindow(true, false, true); } private void CreateNewWindow(bool newThread, bool modal, bool showInTaskbar) { MainWindow mw = this; if (newThread) { Thread thread = new Thread(() => { MainWindow w = new MainWindow(this); w.ShowInTaskbar = showInTaskbar; if (modal) { // ShowDialog automatically starts the event queue for the new windows in the new thread. The window isn't // modal though. w.ShowDialog(); } else { w.Show(); w.Closed += (sender2, e2) => w.Dispatcher.InvokeShutdown(); System.Windows.Threading.Dispatcher.Run(); } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); } else { MainWindow w = new MainWindow(this); w.ShowInTaskbar = showInTaskbar; if (modal) { // Even modal dialogs run in the same thread. w.ShowDialog(); } else { w.Show(); } } } private void OnAccessTest(object sender, RoutedEventArgs e) { if (m_prevWindow == null) { return; } this.Background = Brushes.Lavender; try { m_prevWindow.Background = Brushes.LightBlue; } catch (InvalidOperationException excpt) { MessageBox.Show("Exception: " + excpt.Message, "Invalid Operation"); } m_prevWindow.Dispatcher.Invoke((Action)(() => m_prevWindow.Background = Brushes.Green)); this.Dispatcher.Invoke((Action)(() => { try { m_prevWindow.Background = Brushes.Red; } catch (InvalidOperationException excpt) { MessageBox.Show("Exception: " + excpt.Message, "Invalid Dispatcher Operation"); } })); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With