TOC

The community is working on translating this tutorial into Thai, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".

Misc.:

Cancelling the BackgroundWorker

As we saw in the previous article, the multi-threading has the added advantage of being able to show progress and not having your application hang while performing time-consuming operation.

Another problem you'll face if you perform all the work on the UI thread is the fact that there's no way for the user to cancel a running task - and why is that? Because if the UI thread is busy performing your lengthy task, no input will be processed, meaning that no matter how hard your user hits a Cancel button or the Esc key, nothing happens.

Fortunately for us, the BackgroundWorker is built to make it easy for you to support progress and cancellation, and while we looked at the whole progress part in the previous chapter, this one will be all about how to use the cancellation support. Let's jump straight to an example:

<Window x:Class="WpfTutorialSamples.Misc.BackgroundWorkerCancellationSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="BackgroundWorkerCancellationSample" Height="120" Width="200">
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBlock Name="lblStatus" HorizontalAlignment="Center" Margin="0,10" FontWeight="Bold">Not running...</TextBlock>
        <WrapPanel>
            <Button Name="btnStart" Width="60" Margin="10,0" Click="btnStart_Click">Start</Button>
            <Button Name="btnCancel" Width="60" Click="btnCancel_Click">Cancel</Button>
        </WrapPanel>
    </StackPanel>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

namespace WpfTutorialSamples.Misc
{
	public partial class BackgroundWorkerCancellationSample : Window
	{
		private BackgroundWorker worker = null;

		public BackgroundWorkerCancellationSample()
		{
			InitializeComponent();
			worker = new BackgroundWorker();
			worker.WorkerSupportsCancellation = true;
			worker.WorkerReportsProgress = true;
			worker.DoWork += worker_DoWork;
			worker.ProgressChanged += worker_ProgressChanged;
			worker.RunWorkerCompleted += worker_RunWorkerCompleted;
		}

		private void btnStart_Click(object sender, RoutedEventArgs e)
		{
			worker.RunWorkerAsync();
		}

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

		void worker_DoWork(object sender, DoWorkEventArgs e)
		{
			for(int i = 0; i <= 100; i++)
			{
				if(worker.CancellationPending == true)
				{
					e.Cancel = true;
					return;
				}
				worker.ReportProgress(i);
				System.Threading.Thread.Sleep(250);
			}
			e.Result = 42;
		}

		void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			lblStatus.Text = "Working... (" + e.ProgressPercentage + "%)";
		}

		void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if(e.Cancelled)
			{
				lblStatus.Foreground = Brushes.Red;
				lblStatus.Text = "Cancelled by user...";
			}
			else
			{
				lblStatus.Foreground = Brushes.Green;
				lblStatus.Text = "Done... Calc result: " + e.Result;
			}
		}
	}
}

So, the XAML is very fundamental - just a label for showing the current status and then a couple of buttons for starting and cancelling the worker.

In Code-behind, we start off by creating the BackgroundWorker instance. Pay special attention to the WorkerSupportsCancellation and WorkerReportsProgress properties which we set to true - without that, an exception will be thrown if we try to use these features.

The cancel button simply calls the CancelAsync() method - this will signal to the worker that the user would like to cancel the running process by setting the CancellationPending property to true, but that is all you can do from the UI thread - the rest will have to be done from inside the DoWork event.

In the DoWork event, we count to 100 with a 250 millisecond delay on each iteration. This gives us a nice and lengthy task, for which we can report progress and still have time to hit that Cancel button.

Notice how I check the CancellationPending property on each iteration - if the worker is cancelled, this property will be true and I will have the opportunity to jump out of the loop. I set the Cancel property on the event arguments, to signal that the process was cancelled - this value is used in the RunWorkerCompleted event to see if the worker actually completed or if it was cancelled.

In the RunWorkerCompleted event, I simply check if the worker was cancelled or not and then update the status label accordingly.

Summary

So to sum up, adding cancellation support to your BackgroundWorker is easy - just make sure that you set WorkerSupportsCancellation property to true and check the CancellationPending property while performing the work. Then, when you want to cancel the task, you just have to call the CancelAsync() method on the worker and you're done.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!