In this article WPF MultiThreading, I will demonstrate how to create multi threaded WPF application. You will learn about :
- WPF Threading Model
- Creating separate Thread to offload time consuming tasks to be done asynchronously
- Using WPF provided classes like BackgroundWorker instead of creating separate thread manually to offload time consuming tasks to be done asynchronously
- Updating the UI elements from another thread using Dispatcher class
- Using DispatcherTimer
I will demonstrate by creating a sample C# WPF application and creating a Thread to perform time consuming tasks asynchronously and access UI elements and also by using BackgroundWorker and Dispatcher classes. But before that, let’s go through some concepts first.
Source Code : Complete source code can be downloaded from the following GitHub repo :
https://github.com/sudipta-chaudhari/WPFMultiThreading
WPF works in Single Threaded Apartment (STA) model and works with 2 threads –
- Rendering Thread – hidden and runs in background
- UI Thread – receives user input, handles events and paints screen
Simple WPF applications use single UI thread. However, for time consuming tasks which can block the UI causing it to freeze, need to use separate threads arises. WPF provided classes to execute time consuming tasks in background thread like BackgroundWorker and update the UI using Dispatcher class.
When creating a multithreaded WPF application, if you create a thread and try to access WPF’s UI elements inside the thread or even the BackgroundWorker, you will receive the following error – “The calling thread cannot access this object because a different thread owns it”. This is because threads can’t access objects created in STA. To overcome this and update UI elements from the thread, you need to use Dispatcher class.
[NOTE]: You can continue to use Thread class/TPL library instead of using BackgroundWorker to achieve the same result
In this article, I will describe how to offload time consuming tasks by use of the following: –
- Thread class/TPL Library
- BackgroundWorker
- Dispatcher
- DispatcherTimer
This followed by complete sample application with source code.
(1) Thread class / TPL Library
I believe you are comfortable in creation and use of Threads and using TPL library.
(2) BackgroundWorker
BackgroundWorker class exists in System.ComponentModel namespace. It executes an operation on a separate thread.
In this article, I will focus on the following three events: –
- DoWork – Executes in background thread
- ProgressChanged – Executes when ReportProgress method is called.
- RunWorkerCompleted – Executes when the background operation has completed, has been cancelled, or has raised an exception
For detailed description of all properties, events and methods of Dispatcher class, please visit MSDN – https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx
(3) Dispatcher
Dispatcher class exists in System.Windows.Threading namespace. UI Thread queues work inside Dispatcher object. It doesn’t execute an operation on a separate thread. It manages queue of work items in FIFO manner i.e. queues message calls inside Dispatcher object.
When WPF application is started, new Dispatcher object is created automatically and it’s Run() method is called. Run() method is used for initializing message queue.
WPF works internally with Dispatcher object and we don’t need to work with Dispatcher when we are working on the UI thread.
When we create a new thread for offloading the work and want to update the UI from the other thread then we must need Dispatcher. Only Dispatcher can update the objects in the UI from non-UI thread.
In this article, I will focus on the following event
- Invoke – takes an Action or Delegate and execute the method synchronously
For detailed description of all properties, events and methods of Dispatcher class, please visit MSDN – https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher(v=vs.110).aspx
Let’s now proceed to build the WPF application.
(4) DispatcherTimer
DispatcherTimer class exists in System. Windows.Threading namespace. It is a timer that is integrated into the Dispatcher queue which is processed at a specified interval of time and at a specified priority. It works like the traditional Timer class present in System.Timers namespace, has a Tick event.
For detailed description of all properties, events and methods of Dispatcher class, please visit MSDN – https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer(v=vs.110).aspx
Sample WPF Application
The XAML file has a simple design having a StackPanel and controls places inside it. I have used a ProgressBar whose progress will be update synchronously and asynchronously.
- First button uses BackgroundWorker class to update the progress bar asynchronously – Works fines
- Second button uses System.Timer class and tries to update the ProgressBar – Raises an exception: “The calling thread cannot access this object because a different thread owns it”
- Third button uses DispatcherTimer class to update the progress bar asynchronously – Works fine
- Fourth button updates the progress bar synchronously – Works fine
- Fifth button uses a thread and tries to update the progress bar asynchronously using a thread – Works fine
Source Code
<Window x:Class="WPFMultiThreading.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:WPFMultiThreading" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <StackPanel> <ProgressBar x:Name="pb" HorizontalAlignment="Center" Height="19" VerticalAlignment="Top" Width="470" Margin="10 50 10 10" /> <Button x:Name="btnProgressBackgroundWorker" Content="Show Progress - Backgound Worker" HorizontalAlignment="Center" Margin="10 10 10 10" VerticalAlignment="Top" Width="212" Click="BtnProgressBackgroundWorker_Click"/> <Button x:Name="btnSystemTimer" Content="Show Progress - System.Timer" HorizontalAlignment="Center" Margin="10 10 10 10" VerticalAlignment="Top" Width="212" Click="btnSystemTimer_Click" /> <Button x:Name="btnProgressDispatchTimer" Content="Show Progress - Dispatcher Timer" HorizontalAlignment="Center" Margin="190,10" VerticalAlignment="Top" Width="212" Click="BtnProgressDispatchTimer_Click"/> <Button x:Name="btnProgressSyncronous" Content="Show Progress - Synchronous" Click="BtnProgressSyncronous_Click" Margin="190,10" HorizontalAlignment="Center" VerticalAlignment="Center" Width="212"/> <Button x:Name="btnProgressAsyncronous" Content="Show Progress - Async Thread" Click="BtnProgressAsyncronous_Click" Margin="190,10" HorizontalAlignment="Center" VerticalAlignment="Center" Width="212"/> <Label x:Name="lblProgress" Content="" Margin="10 10 10 10" HorizontalAlignment="Center" VerticalAlignment="Top" Width="600"/> </StackPanel> </Grid> </Window>
using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace WPFMultiThreading { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { BackgroundWorker bg; DispatcherTimer timer; System.Timers.Timer t; public MainWindow() { InitializeComponent(); InitilaizeProgressBar(); InitializeBackgroundWorker(); InitializeSystemTimer(); InitializeDispatchTimer(); } private void InitilaizeProgressBar() { pb.Value = 0; pb.Maximum = 10; } private void InitializeBackgroundWorker() { bg = new BackgroundWorker(); bg.DoWork += Bg_DoWork; bg.ProgressChanged += Bg_ProgressChanged; bg.RunWorkerCompleted += Bg_RunWorkerCompleted; bg.WorkerReportsProgress = true; } private void InitializeDispatchTimer() { timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += Timer_Tick; } private void InitializeSystemTimer() { t = new System.Timers.Timer(1000); t.Elapsed += T_Elapsed; } private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { pb.Value += 1; } catch (Exception ex) { Dispatcher.Invoke(() => { pb.Value = 0; lblProgress.Content = ex.Message; }); } finally { t.Stop(); } } private void Timer_Tick(object sender, EventArgs e) { pb.Value += 1; if (pb.Value == 10) { lblProgress.Content += "nUpdate progress using Dispatch Timer completed!"; timer.Stop(); } } private void Bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { lblProgress.Content += "nUpdate progress using BackgroundWorker completed!"; } private void Bg_ProgressChanged(object sender, ProgressChangedEventArgs e) { pb.Value += 1; } private void Bg_DoWork(object sender, DoWorkEventArgs e) { Dispatcher.Invoke(() => { pb.Value = 0; lblProgress.Content = "Update progress using BackgroundWorker started!"; }); for (int i = 1; i <= 10; i++) { Thread.Sleep(1000); //do some time consuming task bg.ReportProgress(0);//raise background worker's progress changed event } //Dispatcher.Invoke(() => //{ // lblProgress.Content = "nUpdate progress using BackgroundWorker completed!"; //}); } private void BtnProgressBackgroundWorker_Click(object sender, RoutedEventArgs e) { lblProgress.Content = string.Empty; //Using Bakground Worker -> Progress bar value updated at each step asynchronously. bg.RunWorkerAsync(); } private void BtnProgressDispatchTimer_Click(object sender, RoutedEventArgs e) { Dispatcher.Invoke(() => { pb.Value = 0; lblProgress.Content = "Update progress using Dispatch Timer started!"; }); timer.Start(); } private void BtnProgressSyncronous_Click(object sender, RoutedEventArgs e) { pb.Value = 0; lblProgress.Content = "Update progress started synchronously!"; for (int i = 1; i <= 10; i++) { Thread.Sleep(1000); //do some task pb.Value += 1; } lblProgress.Content += "Update progress finished synchronously!"; } private void BtnProgressAsyncronous_Click(object sender, RoutedEventArgs e) { pb.Value = 0; lblProgress.Content = string.Empty; Task.Factory.StartNew(async() => { await Dispatcher.Invoke(async() => { lblProgress.Content = "Update progress started asynchronously using thread!"; for (int i = 0; i < 10; i++) { await Task.Delay(1000); pb.Value += 1; } lblProgress.Content += "nUpdate progress completed asynchronously using thread!"; }); }); } private void btnSystemTimer_Click(object sender, RoutedEventArgs e) { t.Start(); } } }
Source Code Walkthrough
In the MainWindow class constructor, InitilaizeProgressBar()
method initializes the progress bar properties – Value to 0 and Maximum to 10.
Next method in the constructor is InitializeBackgroundWorker()
which creates a new object of BackgroundWorker
class. It also creates the events – DoWork
(Bg_DoWork
), ProgressChanged
(Bg_ProgressChanged
) and RunWorkerCompleted
(Bg_RunWorkerCompleted
).
Bg_DoWork
– Call’s the Dispatcher’s Invoke()
method which sets ProgressBar and Label values. These UI element values can’t be set without the Dispatcher
. Next in the for
loop, Sleep()
method of Thread
class is called to simulate a long running process. Also, ReportProgress()
method of BackgroundWorker
class is called to raise the ProgressChanged
event.
Bg_ProgressChanged
– Increments the ProgressBar value in steps of 1.
Bg_RunWorkerCompleted
– Updates the Label content
The first button’s click event handler calls the RunWorkerAsync()
method of the BackgroundWorker
object to start it. The ProgressBar starts to increment every second and completes after 10 seconds.
Next method InitializeSystemTimer()
creates a new object of System.Timers.Timer
class and sets the timer interval to tick every second. It also registers the Elapsed
event (T_Elapsed
)
T_Elapsed
– Tries to update the ProgressBar value by 1. Since the UI element cannot be accessed, catch block uses Dispatcher
object’s Invoke()
method to reset the ProgressBar value to 0 Label content to the error message.
Finally block stops the timer.
The second button’s click event handler calls the timer object’s Start()
method. An error message is displayed on the label – The calling thread cannot access this object because a different thread owns it.
Next method InitializeDispatchTimer()
creates a new object of DispatcherTimer
class and sets the timer interval to tick every second. It also registers the Tick
event (Timer_Tick
)
In this event handler, the ProgressBar value is incremented in steps of 1. When the ProgressBar value reaches 10, Label content is updated and timer is stopped.
The third button’s click event handler uses Dispatcher
object’s Invoke()
method to set ProgressBar to initial value 0 and updates Label content. Next the DispatchTimer
object is started using Start()
method.
The ProgressBar starts to increment every second and completes after 10 seconds.
The fourth button’s click event handler sets the ProgressBar value to 0 and sets the Label content. Next in the for
loop, Sleep()
method of Thread
class is called to simulate a long running process. The ProgressBar value is incremented in steps of 1 with each loop iteration. Finally, the Label content is updated to display the completion of the operation.
In this case, the UI is blocked till the operation completes and the progress bar is incremented at the end synchronously.
The fifth button’s click event handler sets the ProgressBar value to 0 and sets the Label content to empty. Next an asynchronous thread is created and started using TPL. Inside this thread, Dispatcher’s Invoke
method is called asynchronously. Inside it, Label’s content is set to notify the start of the process. Next, a for
loop is created to iterate 10 times. Inside the for loop, Delay()
method of System.Threading.Tasks.Task
class is called to simulate a long running process with a delay of 1 second. The ProgressBar value is incremented in steps of 1 with each loop iteration. Finally after the for loop, the Label’s content is again set to notify the completion of the process.
With the above 5 scenarios, you must be clear on how to create responsive multi threaded applications in WPF.
Please watch the following video to see how the program works.
This concludes the article – WPF Multithreading. I hope you liked this article. If you have any comments, questions or suggestions, please post them using the comments section below this article. I will try to respond at my earliest or somebody else reading the article and your comment will try to respond.
Please subscribe to my blog via email to get regular updates on the latest articles and share this article over social networks like Facebook, Twitter etc. You can use the social sharing buttons and social sharing bar provided to share this on social media.