C# / WPF

WPF BackgroundWorker

To build more responsive WPF UI, we have to use multiple threads that can work in the background and update the UI when the work completed.

But background threads can’t access the objects created in the STA. Then how can we update the UI when the background threads completed their work? 

WPF applications are based on Single Threaded Apartment Model (STA). Objects created in the STA are not thread safe. They need a way to synchronize their access. STA contains one single thread that manages access to all the objects. If another thread want to interact with the objects, then the message must marshalled to STA thread.

There is a need of a class which execute the time taking and complex computations in the background thread and update the UI from the STA thread when the result comes.

BackgroundWorker Class

BackgroundWorker introduced by WPF team to handle this type of situation. BackgroundWorker class in System.ComponentModel namespace execute the complex computation work in the background thread and update the UI in the UI thread of WPF.

BackgroundWorker important properties and methods are:

  • DoWork : Event handler of this event executes in the background thread.
  • RunWorkerCompleted : Event handler of this event executes on the completion of the work in the UI thread.
  • RunWorkerAsync : Start the BackgroundWorker.
  • ProgressChanged : Event handler of this event executes whenever you call the ReportProgress method in DoWork method. This event handle also executes on the UI thread so that you will show progress of work in the UI.
  • ReportProgress : Send the report progress percentage to the ProgressChanged event handler.
  • WorkerReportsProgress : Indicates whether BackgroundWorker can report progress update or not.
  • WorkerSupportsCancellation : Indicates whether BackgroundWorker can support asynchronous cancellation.
  • CancelAsync : Request Cancellation of background operation. This method is not immediately abort the BackgroundWorker. It just set the CancellationPending property to true. In the DoWork method, you can check if CancellationPending is true then exit from the method.

BackgroundWorker example

In the example, we’ll create a simple WPF UI.

backgroundworker example

When you click on the Start button, it will create a BackgroundWorker, assign event handlers to the DoWork, ProgressChanged, RunWorkerCompleted and call the RunWorkerAsync method.

In the DoWork method, we’ll call the ReportProgress method with the item number added as parameter and added the new ListBoxItem in the ListBox with the number of item added.

In the last when BackgroundWorker completed, it will call the RunWorkerCompleted event handler. In this method, we’ll assign the status textblock to Completed.

Cancel button enable when you start the BackgroundWorker. If you click on the Cancel button in between executing the worker, it’ll call the CancelAsync method and cancel the BackgroundWorker.

Below is the full source code of above example:

<Window x:Class="BackgroundWorker_SampleProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <ListBox Grid.Row="0" x:Name="ListBox1">
            
        </ListBox>
        
        <ProgressBar Grid.Row="1" x:Name="ProgressBar1" Height="20" Margin="5" />
        <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="5">
            <TextBlock Text="Status: " />
            <TextBlock x:Name="StatusTextBox" />
        </StackPanel>
        <Grid Grid.Row="3" Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="1*" />
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0" x:Name="StartButton" Content="Start" Click="StartButton_Click" />
            <Button Grid.Column="1" x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Margin="10,0,0,0" />
        </Grid>
        
    </Grid>
</Window>

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace BackgroundWorker_SampleProject
{
    public partial class MainWindow : Window
    {
        BackgroundWorker worker;

        public MainWindow()
        {
            InitializeComponent();
            CancelButton.IsEnabled = false;
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            ListBox1.Items.Clear();

            StartButton.IsEnabled = false;
            CancelButton.IsEnabled = true;

            worker = new BackgroundWorker();
            worker.DoWork += worker_DoWork;
            worker.ProgressChanged += worker_ProgressChanged;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;

            int maxItems = 50;
            ProgressBar1.Minimum = 1;
            ProgressBar1.Maximum = 100;

            StatusTextBox.Text = "Starting...";
            worker.RunWorkerAsync(maxItems);
        }
        
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            int? maxItems = e.Argument as int?;
            for (int i = 1; i <= maxItems.GetValueOrDefault(); ++i)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }

                Thread.Sleep(200);
                worker.ReportProgress(i);

                //item added
            }
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            worker.CancelAsync();
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                StatusTextBox.Text = "Cancelled";
            }
            else
            {
                StatusTextBox.Text = "Completed";
            }
            StartButton.IsEnabled = true;
            CancelButton.IsEnabled = false;
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            double percent = (e.ProgressPercentage * 100) / 50;

            ProgressBar1.Value = Math.Round(percent, 0);

            ListBox1.Items.Add(new ListBoxItem { Content = e.ProgressPercentage + " item added" });
            StatusTextBox.Text = Math.Round(percent, 0) + "% percent completed";
        }
    }
}
BackgroundWorker ReportProgress
BackgroundWorker Cancelasync

Summary

BackgroundWorker class is used in scenarios when you have to complete time taking task in the background but make the WPF UI responsive in meantime. BackgroundWorker executes its task in the background thread and update the WPF UI in the UI thread when task is completed.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.