C#. Windows Forms. Development of a program for demonstrating work with threads of execution




Development of a program for demonstrating work with threads of execution in Windows Forms applications. BackgroundWorker control. Sorting an array by insertion algorithms, bubble, selection

This topic describes in detail the process of creating a Windows Forms application that implements sorting an array of numbers in three parallel threads. The topic will be useful for novice programmers when studying the features of working with threads of execution based on the BackgroundWorker component (class). The demo application is created in Microsoft Visual Studio 2019.

Before exploring this topic, it is recommended that you familiarize yourself with the following topics:


Contents


Search other resources:

Task

Develop a Windows Forms Application. Implement the following operations in the application:

  • generating an array of random numbers of the specified dimension;
  • sorting the generated array with three algorithms: insert, bubble, selection;
  • visualization of the sorting process by each algorithm;
  • displaying information about the duration of sorting by each algorithm for the purpose of comparison.

Array generating and its sorting must be implemented in different threads. To represent a separate thread, you need to use the capabilities of the BackgroundWorker class (control).

 

Solution

1. Run MS Visual Studio. Save the project

A Windows Forms application is created in a standard way. The process of creating a new project and saving it is described in detail here.

After creation, the system will automatically generate a new form named Form1 (Figure 1).

C#. Windows Forms. Main form of a project

Figure 1. Main form of a project

 

2. Placement of controls on the form

At this stage, you need to place the following controls on the form (Figure 2):

  • 7 controls of Label type named label1, label2, label3, label4, label5, label6, label7;
  • 4 controls of Button type named button1, button2, button3;
  • 3 controls of the ListBox type, which have the names lisbBox1, listBox2, listBox3;
  • 3 controls of type ProgressBar named progressBar1, progressBar2, progressBar3;
  • 4 controls of type BackgroundWorker named backgroundWorker1, backgroundWorker2, backgroundWorker3, backgroundWorker4;
  • one control of type TextBox named textBox1.

 

C#. Windows Forms. Program form after placement of controls

Figure 2. Program form after placement of controls

 

3. Customizing visual form controls

After placing the controls, you need to configure their properties. The following properties of the controls are configured in the Properties window:

  • in the control label1 property Text = “Array size:” (label1.Text = “Array size:”);
  • button1.Text = “Generate array”;
  • button2.Text = “Sort”;
  • button3.Text = “Stop”;
  • label2.Text = “Bubble sorting”;
  • label3.Text = “Insertion sorting”;
  • label4.Text = “Selection sorting”;
  • label5.Text = “”;
  • label6.Text = “”;
  • label7.Text = “”;
  • Form1.Text = “Demo of BackgroundWorker class”.

After configuration, the form will look like shown in Figure 3.

C#. Windows Forms. BackgroundWorker control. Program form after customization

Figure 3. Program form after customization

 

4. Customizing non-visual BackgroundWorker controls

BackgroundWorker controls are non-visual controls. Each control is designed to implement a separate thread. In all four controls, you need to set the properties as follows:

  • property WorkerReportProgress = True. This means that it is allowed to display the progress of the change (progress of the completed work) of the thread;
  • property WorkerSupportsCancellation = True. This means that it is allowed to cancel (stop) the execution of the thread.

 

5. Enter the internal variables in the form class

At this stage, additional internal variables need to be introduced into the form class. In our case, the following arrays are introduced:

  • an array named array of type int[]. This array is the original array. If in the process of sorting the command for canceling the execution of threads is called, then all other undersorted arrays will receive the initial value from this array;
  • the array named arrayBub of type int[]. Used to represent data when sorting by the bubble method;
  • the arrayIns array of type int[] represents data to be sorted by insertion;
  • the arraySel array of type int[] represents data to be sorted by the selection method;
  • additional variables tsBubble, tsIns, tsSel of the TimeSpan type, which save the time for starting sorting by the corresponding algorithm;
  • variables fCancelBub, fCancelIns, fCancelSel. These variables determine whether the command to cancel the execution of the corresponding thread has been invoked (true).

After the entered data, the abbreviated form of the Form1 class of the form is as follows.

public partial class Form1 : Form
{
  // Internal variables
  int[] array; // the original array
  int[] arrayBub; // data array after bubble sort
  int[] arrayIns; // array after insertion sort
  int[] arraySel; // array after selection sort

  // Variables fixing the start time of the execution of algorithms
  TimeSpan tsBubble; // bubble algorithm
  TimeSpan tsIns;   // insertion sorting algorithm
  TimeSpan tsSel;   // selection sorting algorithm

  bool fCancelBub; // If true, then the Stop button was pressed - stop all threads
  bool fCancelIns;
  bool fCancelSel;

  ...

}

 

6. Additional internal methods
6.1. Method DisplayArray(). Displaying an array in ListBox control

The initial state of the array being sorted is reflected in the following controls:

  • listBox1 – bubble sort;
  • listBox2 – insertion sort;
  • listBox3 – selection sort.

Since the operation of displaying an array is the same for any array, it is advisable to introduce a method into the form class that displays the array

public partial class Form1 : Form
{
  ...

  // Internal method that displays an array in a ListBox control
  private void DisplayArray(int[] A, ListBox LB)
  {
    LB.Items.Clear();
    for (int i = 0; i < A.Length; i++)
      LB.Items.Add(A[i]);
  }

  ...
}

 

6.2. Method Active(). Activating/deactivating controls

In the program, you need to ensure that the controls are correctly configured to avoid subtle errors. If the thread that generates the array is executing, then at the moment it is not advisable to start sorting by three algorithms. Because there is not enough data for sorting yet (not the entire array has been formed). To avoid such collisions, the program must correctly set the activity (inactivity) of the corresponding controls.

In our case, the active/inactive controls are set in the Active() method of the Form1 class. Controls are used that are designed to implement and visualize the sorting process.

public partial class Form1 : Form
{
  ...

  // Internal method for activating controls
  private void Active(bool active)
  {
    // Make some controls active/inactive
    label2.Enabled = active;
    label3.Enabled = active;
    label4.Enabled = active;
    label5.Enabled = active;
    label6.Enabled = active;
    label7.Enabled = active;
    listBox1.Enabled = active;
    listBox2.Enabled = active;
    listBox3.Enabled = active;
    progressBar1.Enabled = active;
    progressBar2.Enabled = active;
    progressBar3.Enabled = active;
    button2.Enabled = active;
    button3.Enabled = active;
  }

  ...

}

 

7. Programming event handlers for visual controls
7.1. Loading the form. Form1 constructor

It is advisable to place the code of initial initialization of controls and some internal variables in the form constructor. In our case, the constructor text is as follows

...

public Form1()
{
  InitializeComponent();

  // Clear textBox1 field
  textBox1.Text = "";

  // Clear ListBox
  listBox1.Items.Clear();
  listBox2.Items.Clear();
  listBox3.Items.Clear();

  // Clear ProgressBar
  progressBar1.Value = 0;
  progressBar2.Value = 0;
  progressBar3.Value = 0;

  // Deactivate some controls
  Active(false);

  // Configure internal variables
  fCancelBub = false;
  fCancelIns = false;
  fCancelSel = false;
}

...

 

7.2. The Click event handler for button1. Generate array command

To execute the thread of generating the array, the program uses the backgroundWorker1 control. The thread is started by calling the RunWorkerAsync() method. To avoid restarting a thread that has not yet ended, the backgroundWorker1.isBusy property is used to determine if the thread is currently running.

// Button "Generate array"
private void button1_Click(object sender, EventArgs e)
{
  // Deactivate some controls
  Active(false);

  // Customize labels
  label5.Text = "";
  label6.Text = "";
  label7.Text = "";

  // Start generating an array in a thread
  if (!backgroundWorker1.IsBusy)
    backgroundWorker1.RunWorkerAsync(); // generate the DoWork event
}

 

7.3. The event handler for the Click event of the button2. The command “Sort”

When you press the Sort button (button2), the sorting is carried out by three algorithms. Each algorithm is executed in a separate thread of execution (backgroundWorker2, backgroundWorker3, backgroundWorker4).

// "Sort" button - start threads for execution
private void button2_Click(object sender, EventArgs e)
{
  // Deactivate the generate array button
  button1.Enabled = false;

  // Starting sorting methods in threads
  if (!backgroundWorker2.IsBusy)
    backgroundWorker2.RunWorkerAsync();

  if (!backgroundWorker3.IsBusy)
    backgroundWorker3.RunWorkerAsync();

  if (!backgroundWorker4.IsBusy)
    backgroundWorker4.RunWorkerAsync();
}

 

7.4. The Click event handler for button button3. Stop command

The program has the ability to stop the sorting process in threads using the Stop button (button3). To programmatically cancel the thread of execution, use the CancelAsync() method. This method makes sense if the WorkerSupportsCancellation property is true (see point 4).

// Stop button - cancel the execution of all threads
private void button3_Click(object sender, EventArgs e)
{
  try
  {
    backgroundWorker2.CancelAsync(); // stop bubble sort
    backgroundWorker3.CancelAsync(); // stop the insertion sort
    backgroundWorker4.CancelAsync(); // stop the selection sort
  }
  catch (InvalidOperationException ex)
  {
    MessageBox.Show(ex.Message);
  }
}

 

8. Programming event handlers for BackgroundWorker controls
8.1. The   DoWork event of thread execution

The control’s DoWork event handler inserts code that must be executed in the thread. In our case, we need to insert the sorting code with one of the algorithms (insertion, selection, bubble).

8.1.1. The backgroundWorker1 control’s DoWork event handler. Generating an array

The backgroundWorker1 control’s DoWork event handler implements thread execution in which an array of random numbers of type int is generated. To call the DoWork event handler, follow these steps:

  • activate the backgroundWorker1 control;
  • in the Properties window go to the Events tab;
  • in the list of events, double-click in the DoWork event field.

You can read more about programming an event in Microsoft Visual Studio here.

For our case, the text of the DoWork event handling method is as follows

// Executing a thread that generates an array of numbers
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
  // 1. Declaring internal variables
  Random rnd = new Random();

  // 2. Get the number of elements in the array
  int n = Convert.ToInt32(textBox1.Text);

  // 3. Allocate memory for arrays and fill them with values
  array = new int[n];
  arrayBub = new int[n];
  arrayIns = new int[n];
  arraySel = new int[n];

  for (int i = 0; i < n; i++)
  {
    Thread.Sleep(1);

    array[i] = rnd.Next(1, n + 1); // random number
    arrayBub[i] = arraySel[i] = arrayIns[i] = array[i]; // copy this number

    // Call the display of progress (change) of thread execution
    try
    {
      backgroundWorker1.ReportProgress((i * 100) / n);
    }
    catch (InvalidOperationException ex)
    {
      MessageBox.Show(ex.Message);
      return;
    }
  }
}

 

8.1.2. The backgroundWorker2 control’s DoWork event handler. Bubble sort

The bubble sort is triggered in the DoWork event handler of the backgroundWorker2 control. Following the example of the previous paragraph, you need to generate the code for the DoWork event handler.

// Bubble Sort - thread
private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
  // The arrayBub array is sorted
  int x;

  // Initialize time.
  tsBubble = new TimeSpan(DateTime.Now.Ticks);

  for (int i = 0; i < arrayBub.Length; i++)
  {
    Thread.Sleep(1); // allow other threads to run in parallel

    for (int j = arrayBub.Length - 1; j > i; j--)
    {
      if (arrayBub[j - 1] > arrayBub[j]) // Sort ascending
      {
        x = arrayBub[j];
        arrayBub[j] = arrayBub[j - 1];
        arrayBub[j - 1] = x;
      }
    }

    // Display change progress
    try
    {
      backgroundWorker2.ReportProgress((i * 100) / arrayBub.Length);
    }
    catch(InvalidOperationException ex)
    {
      MessageBox.Show(ex.Message);
      return;
    }

    // Checking if the thread was stopped
    if (backgroundWorker2.CancellationPending)
    {
      fCancelBub = true;
      break;
    }
  }
}

 

8.1.3. The backgroundWorker3 control’s DoWork event handler. Sort by inserts

Insertion sort is implemented in the DoWork event method of the backgroundWorker3 control.

// Sort by inserts
private void backgroundWorker3_DoWork(object sender, DoWorkEventArgs e)
{
  // 1. Declare internal variables
  int x, i, j;

  // Initialize time
  tsIns = new TimeSpan(DateTime.Now.Ticks);

  // 2. Sorting cycle
  for (i = 0; i < arrayIns.Length; i++)
  {
    // allow other threads to run in parallel
    Thread.Sleep(1);

    x = arrayIns[i];

    // Finding the place of an element in a sequence
    for (j = i - 1; j >= 0 && arrayIns[j] > x; j--)
      arrayIns[j + 1] = arrayIns[j]; // right shift the element

    arrayIns[j + 1] = x;

    // Display change progress
    try
    {
      backgroundWorker3.ReportProgress((i * 100) / arrayIns.Length);
    }
    catch (InvalidOperationException ex)
    {
      MessageBox.Show(ex.Message);
      return;
    }

    // Checking if the thread was stopped
    if (backgroundWorker3.CancellationPending)
    {
      fCancelIns = true;
      break;
    }
  }
}

 

8.1.4. The backgroundWorker4 control’s DoWork event handler. Sort by selection

Selection sorting is performed on the thread to which the backgroundWorker4 control’s DoWork event handler corresponds.

// Sort by selection
private void backgroundWorker4_DoWork(object sender, DoWorkEventArgs e)
{
  // 1. Declare variables
  int i, j, k;
  int x;

  // 2. Set the initial time
  tsSel = new TimeSpan(DateTime.Now.Ticks);

  // 3. The loop of selection sorting
  for (i = 0; i < arraySel.Length; i++)
  {
    // allow other threads to run in parallel
    Thread.Sleep(1);

    k = i;

    // smallest element search
    x = arraySel[i];

    for (j = i + 1; j < arraySel.Length; j++)
      if (arraySel[j] < x)
      {
        k = j; // k - index of the smallest element
        x = arraySel[j];
      }

    // swap smallest element with arraySel[i]
    arraySel[k] = arraySel[i];
    arraySel[i] = x;

    // Show progress change
    try
    {
      backgroundWorker4.ReportProgress((i * 100) / arraySel.Length);
    }
    catch (InvalidOperationException ex)
    {
      MessageBox.Show(ex.Message);
      return;
    }

    // Checking if the thread was stopped
    if (backgroundWorker4.CancellationPending)
    {
      fCancelSel = true;
      break;
    }
  }
}

 

8.2. ProgressChanged event. Changing the progress of a thread

In the event handler for the ProgressChanged event, code is inserted to reflect the progress of the work that has been done so far. In our case, the progress of the work done is displayed in controls of the ProgressBar type. The percentage of work completed is displayed in controls of type Label.

8.2.1. The handler for the ProgressChanged event of the backgroundWorker1 control. Displaying array generation progress

More details about event programming in Microsoft Visual Studio are described here. In our case, the text of the event handler for the ProgressChanged event of the backgroundWorker1 control looks like this:

// Change (progress) of the work done in the array generation thread
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  // Display changes to the text "Generate array" button
  button1.Text = "Generate array " + e.ProgressPercentage.ToString() + "%";
}

 

8.2.2. The handler for the ProgressChanged event of the backgroundWorker2 control. Display progress when sorting by bubble

The progress for backgroundWorker2 is also formed following the example of the previous handler. This progress displays the completed portion of the bubble sort.

// Change progress in bubble sort method
private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  label5.Text = Convert.ToString(e.ProgressPercentage) + " %";
  progressBar1.Value = e.ProgressPercentage;
}

 

8.2.3. A handler for the ProgressChanged event of the backgroundWorker3 control. Displaying progress when sorting by inserts

For the insertion sort method, the ProgresChanged event handler is

// Progress for the insertion sort method
private void backgroundWorker3_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  label6.Text = Convert.ToString(e.ProgressPercentage) + " %";
  progressBar2.Value = e.ProgressPercentage;
}

 

8.2.4. A handler for the ProgressChanged event of the backgroundWorker3 control. Displaying progress in the selection sort method

For sorting by selection, the ProgressChanged event handler is as follows

// Changing the progress for the selection sorting algorithm
private void backgroundWorker4_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  label7.Text = Convert.ToString(e.ProgressPercentage) + " %";
  progressBar3.Value = e.ProgressPercentage;
}

 

8.3. RunWorkerCompleted event. Stopping the execution of a thread

The RunWorkerCompleted event of the BackgroundWorker control is raised after the thread has finished executing. In this event, it is advisable to write the code of the final operations to be performed after the end of the thread.

8.3.1. The event handler RunWorkerCompleted of backgroundWorker1 control

 

// Actions after the end of a thread that generates an array of numbers
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // After the array is generated, make the appropriate settings
  button1.Text = "Generate array";

  // Make visible active controls
  Active(true);

  // Display the original array in controls of type ListBox
  DisplayArray(array, listBox1);
  DisplayArray(array, listBox2);
  DisplayArray(array, listBox3);
}

 

8.3.2. The backgroundWorker2 control’s RunWorkerCompleted event handler. Bubble sorting thread ending

 

// Bubble sort finish - perform finish operations
private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // If sorting was canceled
  if (fCancelBub)
  {
    // Customize controls accordingly
    label5.Text = "";

    // Display original array
    DisplayArray(array, listBox1);

    fCancelBub = false;
  }
  else
  {
    // Fix the time and display it
    TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsBubble;
    label5.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes,
    time.Seconds, time.Milliseconds);

    // Display the sorted array
    DisplayArray(arrayBub, listBox1);
  }

  // Configure other controls
  progressBar1.Value = 0;
  button1.Enabled = true;
}

 

8.3.3. The backgroundWorker3 control’s RunWorkerCompleted event handler. Terminating the thread of execution when sorting by inserts

 

// Ending a sort thread with inserts
private void backgroundWorker3_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // If sorting was canceled
  if (fCancelIns)
  {
    // Customize controls accordingly
    label6.Text = "";

    // Display the original array
    DisplayArray(array, listBox2);

    fCancelIns = false;
  }
  else
  {
    // Fix the time and display it
    TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsIns;
    label6.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes,
    time.Seconds, time.Milliseconds);

    // Display the sorted array
    DisplayArray(arrayIns, listBox2);
  }

  // Customize other controls
  progressBar2.Value = 0;
  button1.Enabled = true;
}

 

8.3.4. The RunWorkerCompleted event handler of backgroundWorker4 controls. Ending the sort thread with a selection

 

// Completion of sorting by selection
private void backgroundWorker4_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // If sorting was canceled
  if (fCancelSel)
  {
    // Customize controls accordingly
    label7.Text = "";

    // Display original array
    DisplayArray(array, listBox3);

    fCancelSel = false;
  }
  else
  {
    // Fix the time and display it
    TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsSel;
    label7.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes,
    time.Seconds, time.Milliseconds);

    // Display sorted array
    DisplayArray(arraySel, listBox3);
  }

  // Customize other controls
  progressBar3.Value = 0;
  button1.Enabled = true;
}

 

9. Run the program. Testing

 

C#. Windows Forms. BackgroundWorker control. Sorting process. Parallel execution of threads

Figure 4. Sorting process. Parallel execution of threads