C#: Using the Background Worker to thread your application processing.

In this article I go over using the background worker control in C# .NET Framework (Visual Studio 2015). This control allows you to easily do processing intensive tasks without locking up your interface thread. I use this a lot in winforms applications where I expect code to take any amount of time that the user would notice the interface being locked up while processing. This control gives you the ability to send progress updates to the interface thread as well as cancel processing any any time.

The code for this example is on my GitHub here.

https://www.youtube.com/watch?v=PKKRAyn3TTs

Here is the important code from the main form:


public enum CurrentStatus
{
    None,
    Reset,
    Loading,
    Cancelled,
    Success,
    Busy
}

public class MainForm : Form
{
    private BackgroundWorker bwInstance;
    private Button btnStartWorker;
    private Button btnStopWorker;
    private Label lblLoadingStatus;
    private ProgressBar prgLoadingProgress;

    private CurrentStatus processingStatus = CurrentStatus.None;

    public MainForm()
    {
        InitializeComponents();
        bwInstance = new BackgroundWorker();
        bwInstance.WorkerReportsProgress = true;
        bwInstance.DoWork += bwInstance_DoWork;
        bwInstance.ProgressChanged += bwInstance_ProgressChanged;
        bwInstance.RunWorkerCompleted += bwInstance_RunWorkerCompleted;
    }

    private void InitializeComponents()
    {
        // Initialize UI components here
    }

    private void setFormControlsBasedOnStatus()
    {
        switch (processingStatus)
        {
            case CurrentStatus.None:
                break;
            case CurrentStatus.Reset:
                btnStartWorker.Enabled = true;
                btnStopWorker.Enabled = false;
                lblLoadingStatus.Visible = false;
                lblLoadingStatus.Text = "Loading...";
                prgLoadingProgress.Value = 0;
                break;
            case CurrentStatus.Loading:
                btnStartWorker.Enabled = false;
                btnStopWorker.Enabled = true;
                lblLoadingStatus.Visible = true;
                lblLoadingStatus.Text = "Loading...";
                prgLoadingProgress.Value = 0;
                break;
            case CurrentStatus.Cancelled:
                btnStartWorker.Enabled = true;
                btnStopWorker.Enabled = false;
                lblLoadingStatus.Visible = true;
                lblLoadingStatus.Text = "Cancelled";
                prgLoadingProgress.Value = 0;
                break;
            case CurrentStatus.Success:
                btnStartWorker.Enabled = true;
                btnStopWorker.Enabled = false;
                lblLoadingStatus.Visible = true;
                lblLoadingStatus.Text = "Success!";
                prgLoadingProgress.Value = 0;
                break;
            case CurrentStatus.Busy:
                btnStartWorker.Enabled = true;
                btnStopWorker.Enabled = false;
                lblLoadingStatus.Visible = true;
                lblLoadingStatus.Text = "The worker is busy.";
                prgLoadingProgress.Value = 0;
                break;
            default:
                break;
        }
    }

    private bool isWorkerBeingCancelled(BackgroundWorker worker, DoWorkEventArgs e)
    {
        bool returnValue = false;
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            returnValue = true;
            processingStatus = CurrentStatus.Cancelled;
        }
        return returnValue;
    }

    private void bwInstance_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        if (isWorkerBeingCancelled(worker, e)) return;

        worker.ReportProgress(20);
        System.Threading.Thread.Sleep(250);

        if (isWorkerBeingCancelled(worker, e)) return;
        worker.ReportProgress(40);
        System.Threading.Thread.Sleep(250);

        if (isWorkerBeingCancelled(worker, e)) return;
        worker.ReportProgress(60);
        System.Threading.Thread.Sleep(250);

        if (isWorkerBeingCancelled(worker, e)) return;
        worker.ReportProgress(80);
        System.Threading.Thread.Sleep(250);

        if (isWorkerBeingCancelled(worker, e)) return;
        worker.ReportProgress(100);
        System.Threading.Thread.Sleep(250);

        processingStatus = CurrentStatus.Success;
    }

    private void bwInstance_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        prgLoadingProgress.Value = e.ProgressPercentage;
    }

    private void bwInstance_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        setFormControlsBasedOnStatus();
    }

    private void btnStartWorker_Click(object sender, EventArgs e)
    {
        if (!bwInstance.IsBusy)
        {
            processingStatus = CurrentStatus.Loading;
            setFormControlsBasedOnStatus();
            bwInstance.RunWorkerAsync();
        }
        else
        {
            processingStatus = CurrentStatus.Busy;
            setFormControlsBasedOnStatus();
        }
    }

    private void btnStopWorker_Click(object sender, EventArgs e)
    {
        bwInstance.CancelAsync();
    }
}

I made a status enumeration with a form-level variable that holds the current processing status. I also have a function to update the interface based on that status enum. The background worker is called to see if is busy, if not, the worker is started. Throughout processing, we check to see if the user cancelled the work and also update the progress control by pushing a numeric value (0-100 to match our progress control) through the worker into the interface thread. When the cancel button is clicked, it sets a value in the background worker that we can track to stop processing as needed. I have a function that is called throughout processing that will break out of the processing function and set our enum status variable telling the system that processing was cancelled.