Running an Intensive Process while keeping Windows Form UI Responsive

By | May 6, 2014

Wow its been a long time since I wrote a Windows Form application, I think I already forgot how to develop one as I now default all development to Web MVC format.

Several days ago I was tasked to do a bulk copy of files with some smarts embedded to it as we are migrating some File Servers, at first I was thinking why not just copy paste or buy a third-party product? Sadly that can’t happen as there are some business logic that needs to be implemented as we copy the files so as a quick and easy way to implement this I decided I create a Windows Form application.  At first I thought of creating a simple console application but I wanted to give a decent UI so I can report on its progress to the one who will be using them, so Win Forms it is.  At first I directly coded everything in the button click event and refresh the labels as it changed, having said that the UI was totally unresponsive, it looks like the application had frozen, I get the (Not Responding) message and I cannot drag my window around, while I know it still works at the back it is not good as you don’t really know whats the progress.


Then suddenly I remember I have to separate the heavy processing to another thread from the UI for it to become responsive so I looked at my old snippet library and found this.  To separate both the UI thread to the Process thread you just need to use a BackgroundWorker and give it the load relieving the UI thread of the heavy process.

01 EXE Screen

To use it is as simple as calling

backgroundWorker1.RunWorkerAsync();

And perform your intensive process on the DoWork Event

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

Now how do you report on its progress? Its as simple as calling the ReportProgress like such

BackgroundWorker worker = sender as BackgroundWorker;
string[] CurrentStatus = new string[1];
 
CurrentStatus[0] = "Your Message Here";
worker.ReportProgress(YourPercentageHere, CurrentStatus);

Which in turn calls the event ProgressChanged


private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)

And from here you can increment your progress bar

progressBar1.Value = e.ProgressPercentage;

and also updates any labels that you might be using like such

string[] StatusMsg = (string[])e.UserState;
yourLabel.Text = StatusMsg[0];

Now how do you cancel the processing if needed? Well again its as simple as calling CancelAsync

backgroundWorker1.CancelAsync();

This triggers a cancel task where you can test while you do your intensive task by doing something like this

if (worker.CancellationPending == true)
{
    e.Cancel = true;
    break;
}

Which then calles the Event RunWorkerCompleted

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

Once you’ve done this correctly the application form window will run butter smooth and the progress will be reported to the UI in a very responsive manner.  To give more clarity I will give you a code sample below but please take note I removed some parts of it for brevity.

The main intention of the form below is to perform a copy paste of a whole large directory to another location and will demonstrate running this intensive process in a separate thread so UI will run smooth without hanging.

02 Background Worker

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
 
    private IEnumerable<DirectoryInfo> varyLargeWindowsDirectory = new DirectoryInfo("{Your Source Directory}")
                    .EnumerateDirectories("*"SearchOption.TopDirectoryOnly)
                    .Where(x => !x.Name.EndsWith(SystemSettings.CopyDirectorySuffix()));
 
    private string[] CurrentStatus = new string[2];
    private string sourceFileDirectory = "{Your Source File Directory}";
    private string destinationFileDirectory = "{Your Destination File Directory}";
 
    private void Form1_Load(object sender, EventArgs e)
    {
        progressBar1.Hide();
 
        //Instansiate Background Worker Thread
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.WorkerSupportsCancellation = true;
    }
 
    private void startAsyncButton_Click(object sender, EventArgs e)
    {
        if (backgroundWorker1.IsBusy != true)
        {
            progressBar1.Show();
 
            startAsyncButton.Enabled = false;
            cancelAsyncButton.Enabled = true;
 
            //Start the Asynchronous Operation.
            backgroundWorker1.RunWorkerAsync();
        }
    }
 
    private void cancelAsyncButton_Click(object sender, EventArgs e)
    {
        if (backgroundWorker1.WorkerSupportsCancellation == true)
        {
            // Cancel the Asynchronous Operation.
            backgroundWorker1.CancelAsync();
        }
    }
 
    // This event handler is where the intensive process is performed. 
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
 
        float totalItems = (float)varyLargeWindowsDirectory.Count();
        float itemCounter = 0;
 
        foreach (DirectoryInfo currentDirectory in varyLargeWindowsDirectory)
        {
            if ((worker.CancellationPending == true))
            {
                e.Cancel = true;
                break;
            }
 
            itemCounter = itemCounter + 1;
            float percentage = itemCounter / totalItems;
 
            CurrentStatus[0] = "Processing " + currentDirectory.Name;
            worker.ReportProgress((int)(percentage * 100), CurrentStatus);
 
            //Copy all the files & Replaces any files with the same name
            foreach (string sourceFilePath in Directory.GetFiles(currentDirectory.FullName, "*.*"SearchOption.AllDirectories))
            {
                CurrentStatus[1] = "Copying " + sourceFilePath;
                worker.ReportProgress((int)(percentage * 100), CurrentStatus);
 
                File.Copy(sourceFilePath, sourceFilePath.Replace(sourceFileDirectory, destinationFileDirectory), true);
 
            }
 
            worker.ReportProgress((int)(percentage * 100), CurrentStatus);
        }
 
        MessageBox.Show("Processing Stopped or Finished, Application will now close");
        Application.Exit();
    }
 
    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the Progress Bar and Labels on Progress Report
        if (typeof(string[]) == e.UserState.GetType())
        {
            string[] StatusMessage = (string[])e.UserState;
            if (2 == StatusMessage.GetLength(0))
            {
                // Update the Status lLabels
                lblDirectoryStatus.Text = StatusMessage[0];
                lblFileStatus.Text = StatusMessage[1];
            }
        }
 
        // Update Progress Bar Percentage 
        this.progressBar1.Value = e.ProgressPercentage;
    }
 
    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // Background Worker Cancelled
        if ((e.Cancelled == true))
        {
            lblDirectoryStatus.Text = "Canceled!";
            MessageBox.Show("Processing Cancelled, Application will now close""Cancelled");
        }
        // Background Worker Error
        else if (!(e.Error == null))
        {
            lblDirectoryStatus.Text = ("Error: " + e.Error.Message);
        }
        else
        {
            lblDirectoryStatus.Text = "Done!";
        }
    }
}

Its as easy as that! Happy Coding


Leave a Reply