Here is a simple example on how to use the BackgroundWorker class in C#.
What is the BackgroundWorker?
Are you writing a Windows program with some heavy calculations going on in the background? The BackgroundWorker class will take those calculations and put them in a separate thread, helping to prevent your UI from freezing up.
Multi-threaded programming is tricky at the best of times. The fundamental problem is that you don’t want two threads trying to access the same bit of memory at once. The BackgroundWorker simplifies a lot of the work you would otherwise need to do yourself, but it can still be difficult to set it up.
BackgroundWorker example
This example downloads an image from the Internet and saves it to the user’s desktop. It does this 10 times (so we can see how to monitor progress). The image address is provided by the user in a WPF form, and we report the number of files downloaded in a progress bar on that form.
The form stays responsive the whole time, and doesn’t lock up while the files are being downloaded – something which would happen if we didn’t create our own thread.
Example code
Create a new C# WPF project in Visual Studio.
XAML
We need to create a form with a textbox, a button, a progress bar and a label. This isn’t essential to the BackgroundWorker, but it will help create our example program.
If you aren’t familiar with WPF or XAML, XAML is the markup language used to create interfaces in WPF, much like HTML to web development. Here is the contents of my MainWindow.XAML used to create the form above.
<Window x:Class="BackgroundWorkerExample.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:BackgroundWorkerExample" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <DockPanel> <DockPanel LastChildFill="True" DockPanel.Dock="Top" Margin="10"> <Label DockPanel.Dock="Left" Width="100">URL</Label> <Button Name="btnGo" Click="btnGo_Click" DockPanel.Dock="Right" Width="100">Go</Button> <TextBox Name="tbURL"/> </DockPanel> <DockPanel LastChildFill="True" DockPanel.Dock="Top" Margin="10"> <Label DockPanel.Dock="Left" Width="100">Progress</Label> <ProgressBar DockPanel.Dock="Bottom" Name="progBar" Value="0" /> </DockPanel> <DockPanel LastChildFill="True"> <Label DockPanel.Dock="Top">Status</Label> <ScrollViewer><Label Name="lblStatus" /></ScrollViewer> </DockPanel> </DockPanel> </Window>
C# code
The actual code goes into MainApplication.xaml.cs. Here is the contents of my file.
It looks pretty long, but it’s not so bad. A lot of it is actually comments…
using System; using System.Windows; using System.ComponentModel; using System.IO; namespace BackgroundWorkerExample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btnGo_Click(object sender, RoutedEventArgs e) { lblStatus.Content = ""; BackgroundWorker worker = new BackgroundWorker(); //BackgroundWorker is event-driven. We use events to control what happens //during and after calculations. //First, we need to set up the different events. worker.DoWork += Worker_DoWork; worker.ProgressChanged += Worker_ProgressChanged; worker.WorkerReportsProgress = true; worker.RunWorkerCompleted += Worker_RunWorkerCompleted; //Then, we set the Worker off. //This triggers the DoWork event. //Notice the word Async - it means that Worker gets its own thread, //and the main thread will carry on with its own calculations separately. //We can pass any data that the worker needs as a parameter. worker.RunWorkerAsync(tbURL.Text); } private void Worker_DoWork(object sender, DoWorkEventArgs e) { //DoWork is the most important event. It is where the actual calculations are done. System.Net.WebClient client = new System.Net.WebClient(); for (int i = 0; i < 10; i++) { //We pass data to the worker using the Argument property. //Don't try to read data from the form directly. string url = (string)e.Argument; //download image byte[] imageStream = client.DownloadData(url); MemoryStream memoryStream = new MemoryStream(imageStream); System.Drawing.Image img = System.Drawing.Image.FromStream(memoryStream); //save image to computer string desktop = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop); img.Save(desktop + @"\image " + i.ToString() + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); //Now that the image is saved, we can update the Worker's progress. //We do this by going back to the Worker with a cast int progress = (i + 1) * 10; //Between 0-100 ((BackgroundWorker)sender).ReportProgress(progress); } //When finished, the thread will close itself. We don't need to close or stop the thread ourselves. } private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { //This method is called whenever we call ReportProgress() //Note that progress is not calculated automatically. //We need to calculate the progress ourselves inside Worker_DoWork. //This method is optional. lblStatus.Content += e.ProgressPercentage.ToString() + "% complete. \n"; progBar.Value = e.ProgressPercentage; } private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //This method is optional but very useful. //It is called once Worker_DoWork has finished. lblStatus.Content += "All images downloaded successfully."; progBar.Value = 0; } } }
Running the program
Here is a picture of a satsuma.
Here is its URL:
http://james-ramsden.com/wp-content/uploads/2016/05/satsuma-mandarin1.jpg
If we put this URL into this program, we should be able to download this image 10 times over onto our desktop.
Here it is downloading the images, depositing lots of satsumas on my desktop. Or are they mandarins? I don’t really know.
You may also want to try downloading some larger images that take longer to download. The BackgroundWorker works as expected. The interface is updated with each successful download. (If we didn’t use the BackgroundWorker, the interface would freeze until all 10 had been downloaded.) And the interface remains responsive the entire time.
Further reading
There are many ways to multithread in .NET, and the correct choice depends on what you are trying to do.
For instance, I recently had heavy calculations on a problem in Grasshopper, on entirely back-end code. This was dealt with by using Parallel.ForEach loops, which converts a ForEach loop into a multi-threaded equivalent.
If you’re looking to calculate something as quickly as possible using all the cores on your machine, then this is a good place to start. This approach depends on your task being able to be divided up into independent subtasks, so that each subtask can be calculated separately. (If one subtask depends on the output of another, it is going to be difficult to multithread.)
All in all, multithreading is a big topic, with many approaches possible, and there are many ways you can unfortunately get it wrong. It’s not something you are going to master in a day. But it’s worth spending the time on it, and it’s very rewarding when you do get it working 🙂