We have all worked with single-threaded & multi-threaded applications.
A WPF application always starts in a single-threaded apartment. Now, what is this single-threaded apartment?
Single-threaded apartment (STA)
As the name suggests single-threaded apartment contains only a single thread. And all the objects inside this apartment are managed by this thread. Objects don't need to be synchronized because all method calls are synchronous. So what if another thread tries to access an object of an STA application such as WPF?
.Net throws the following exception,
Let's create a login page to understand what are we talking about.
MainWindow.xaml
- <Window x:Class="A.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:A"
- mc:Ignorable="d"
- Title="MainWindow" Height="120" Width="250">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="Auto"/>
- </Grid.ColumnDefinitions>
- <Label x:Name="LabelUserName"
- Content="User Name:"/>
- <Label x:Name="LabelPassword"
- Content="Password:"
- Grid.Row="1"/>
- <TextBox x:Name="TextBoxUserName"
- Height="20"
- Width="150"
- Grid.Column="1"/>
- <PasswordBox x:Name="TextBoxPassword"
- Height="20"
- Width="150"
- Grid.Column="1"
- Grid.Row="1"/>
- <Button x:Name="ButtonLogin"
- Height="20"
- Width="100"
- Content="Login"
- HorizontalAlignment="Center"
- Margin="20 0 0 0"
- Grid.Row="2"
- Grid.ColumnSpan="2" Click="ButtonLogin_Click"/>
- </Grid>
- </Window>
Let's update code behind MainWindow.xaml.cs
- using System.Drawing;
- using System.Threading;
- using System.Windows;
- using System.Windows.Media;
-
- namespace A
- {
-
-
-
- public partial class MainWindow : Window
- {
- Thread MTA;
- public MainWindow()
- {
-
- InitializeComponent();
- this.DataContext = new MainWindowViewModel();
- }
-
- private void ButtonLogin_Click(object sender, RoutedEventArgs e)
- {
- MTA = new Thread(new ThreadStart(Login));
- MTA.Start();
- }
-
- private void Login()
- {
- if(TextBoxUserName.Text != null)
- {
-
- }
- }
- }
- }
Now inside a Login method is access by another thread, and it is trying to access TextBox's text property which is owned by our STA - WPF application.
Now if we try to run our application we will get the following error,
Solution
WPF app needs a message queue to synchronize calls from other threads.
When other threads call an object in STA thread then the object call is queued in the message queue and STA; i.e., our WPF app, will receive a call from that message queue.
Dispatcher owns a message queue.
How it works
At runtime WPF application automatically creates a new dispatcher object and calls its run method.
The run method is used for initializing the message queue.
We know the WPF app runs on UI Thread, which is responsible for Animations, Control styles & data inputs.
WPF Dispatcher belongs to the UI thread. So in our example when we tried to access TextBox's Text property which was owned by a UI thread, then UI Thread queued the method call into the Dispatcher queue. The dispatcher then executes its message queue in a synchronized fashion.
Purpose of Dispatcher
We are good until we are only working with UI thread.
When we create a new thread to share the load of a UI Thread and want to update the UI from the Non-UI Thread then we have to use a dispatcher.
As we learned, only dispatcher can update the objects owned by UI Thread from a Non-UI Thread.
How to apply dispatcher in WPF
You can use the Invoke method of Dispatcher class and pass anonymous method or your method call.
- using System.Drawing;
- using System.Threading;
- using System.Windows;
- using System.Windows.Media;
-
- namespace A
- {
-
-
-
- public partial class MainWindow : Window
- {
- Thread MTA;
- public MainWindow()
- {
- InitializeComponent();
- this.DataContext = new MainWindowViewModel();
- }
-
- private void ButtonLogin_Click(object sender, RoutedEventArgs e)
- {
- this.Dispatcher.Invoke(() => {
- Login();
- });
- }
-
- private void Login()
- {
- if(TextBoxUserName.Text != null)
- {
-
- }
- }
- }
- }
Now if you run your program, you'll get no error: See the following image shows breakpoint has reached the access point where we were getting error previously.
Here is the output,
Now Invoke method runs synchronously, meaning it will wait until our Login method finishes its execution, then it will move forward with its execution.
But in heavy applications, we can't sit and wait until thread finishes its job.
For that purpose, Dispatcher provides another method BeginInvoke().
Now, this method runs asynchronously, meaning it will not wait until Login method finishes its execution, it will move forward with its execution without waiting.
BeginInvoke method returns the status of an ongoing operation, plus two events aborted and completed. All this is wrapped inside a DispatcherOperation object.
In order to call BeingInvoke you need to pass Action,
- private void ButtonLogin_Click(object sender, RoutedEventArgs e)
- {
- this.Dispatcher.BeginInvoke((Action)(() => {
- Login();
- }));
- }
Well, that's all folks.
I hope you've come away from this with a real grasp of the Dispatcher and how it can be applied to your personal projects in the future.
If you've enjoyed the article, which I hope you have, please apply your knowledge to enliven the code in your projects.
If you have any questions or just want to connect, follow these links.
Happy coding folks.