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:
- Control (component) BackgroundWorker. Working with threads, background operations. Overview of methods, properties, events
- Displays the progress of completed work. Cancelling the execution of a thread
Contents
- Task
- Solution
- 1. Run MS Visual Studio. Save the project
- 2. Placement of controls on the form
- 3. Customizing visual form controls
- 4. Customizing non-visual BackgroundWorker controls
- 5. Enter the internal variables in the form class
- 6. Additional internal methods
- 7. Programming event handlers for visual controls
- 8. Programming event handlers for BackgroundWorker controls
- 8.1. The DoWork event of thread execution
- 8.1.1. The backgroundWorker1 control’s DoWork event handler. Generating an array
- 8.1.2. The backgroundWorker2 control’s DoWork event handler. Bubble sort
- 8.1.3. The backgroundWorker3 control’s DoWork event handler. Sort by inserts
- 8.1.4. The backgroundWorker4 control’s DoWork event handler. Sort by selection
- 8.2. ProgressChanged event. Changing the progress of a thread
- 8.2.1. The handler for the ProgressChanged event of the backgroundWorker1 control. Displaying array generation progress
- 8.2.2. The handler for the ProgressChanged event of the backgroundWorker2 control. Display progress when sorting by bubble
- 8.2.3. A handler for the ProgressChanged event of the backgroundWorker3 control. Displaying progress when sorting by inserts
- 8.2.4. A handler for the ProgressChanged event of the backgroundWorker3 control. Displaying progress in the selection sort method
- 8.3. RunWorkerCompleted event. Stopping the execution of a thread
- 8.3.1. The event handler RunWorkerCompleted of backgroundWorker1 control
- 8.3.2. The backgroundWorker2 control’s RunWorkerCompleted event handler. Bubble sorting thread ending
- 8.3.3. The backgroundWorker3 control’s RunWorkerCompleted event handler. Terminating the thread of execution when sorting by inserts
- 8.3.4. The RunWorkerCompleted event handler of backgroundWorker4 controls. Ending the sort thread with a selection
- 8.1. The DoWork event of thread execution
- 9. Run the program. Testing
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).
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.
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.
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
Figure 4. Sorting process. Parallel execution of threads
⇑