Разработка программы демонстрации работы с потоками выполнения в приложениях типа Windows Forms. Элемент управления BackgroundWorker. Сортировка массива алгоритмами вставки, пузырьком, выбором
В данной теме подробно описывается процесс создания Windows Forms приложения, которое реализует сортировку массива чисел в трех параллельных потоках. Тема будет полезна начинающим программистам при изучении особенностей работы с потоками выполнения, основанных на компоненте (классе) BackgroundWorker. Демонстрационное приложение создается в системе Microsoft Visual Studio 2019.
Перед изучением данной темы рекомендуется ознакомиться со следующими темами:
- Элемент управления BackgroundWorker. Работа с потоками (фоновыми операциями). Обзор методов, свойств, событий
- Отображение прогресса выполненных работ. Отмена потока выполнения
Содержание
- Условие задачи
- Решение
- 1. Запустить MS Visual Studio. Сохранить проект
- 2. Размещение элементов управления на форме
- 3. Настройка визуальных элементов управления формы
- 4. Настройка невизуальных элементов управления типа BackgroundWorker
- 5. Ввод внутренних переменных в класс формы
- 6. Дополнительные внутренние методы
- 7. Программирование обработчиков событий визуальных элементов управления
- 8. Программирование обработчиков событий элементов управления типа BackgroundWorker
- 8.1. Событие DoWork выполнения потока
- 8.1.1. Обработчик события DoWork элемента управления backgroundWorker1. Генерирование массива
- 8.1.2. Обработчик события DoWork элемента управления backgroundWorker2. Сортировка методом пузырька
- 8.1.3. Обработчик события DoWork элемента управления backgroundWorker3. Сортировка вставками
- 8.1.4. Обработчик события DoWork элемента управления backgroundWorker4. Сортировка выбором
- 8.2. Событие ProgressChanged. Изменение прогресса выполнения потока
- 8.2.1. Обработчик события ProgressChanged элемента управления backgroundWorker1. Отображение прогресса генерирования массива
- 8.2.2. Обработчик события ProgressChanged элемента управления backgroundWorker2. Отображение прогресса при сортировке пузырьком
- 8.2.3. Обработчик события ProgressChanged элемента управления backgroundWorker3. Отображение прогресса при сортировке вставками
- 8.2.4. Обработчик события ProgressChanged элемента управления backgroundWorker3. Отображение прогресса в методе сортировки выбором
- 8.3. Событие RunWorkerCompleted. Остановка выполнения потока
- 8.3.1. Обработчик события RunWorkerCompleted элемента управления backgroundWorker1
- 8.3.2. Обработчик события RunWorkerCompleted элемента управления backgroundWorker2. Завершение потока сортировки пузырьком
- 8.3.3. Обработчик события RunWorkerCompleted элемента управления backgroundWorker3. Завершение потока выполнения при сортировке вставками
- 8.3.4. Обработчик события RunWorkerCompleted элемента управления backgroundWorker4. Завершение потока сортировки выбором
- 8.1. Событие DoWork выполнения потока
- 9. Запуск программы. Тестирование
- Связанные темы
Поиск на других ресурсах:
Условие задачи
Разработать приложение типа Windows Forms Application. В приложении реализовать следующие операции:
- генерирование массива случайных чисел указанной размерности;
- сортировку сгенерированного массива тремя алгоритмами: вставки, пузырьком, выбором;
- визуализацию процесса сортировки каждым алгоритмом;
- вывод информации о продолжительности сортировки каждым алгоритмом с целью сравнения.
Генерирование массива и его сортировку реализовать в различных потоках. Для представления отдельного потока использовать возможности класса (элемента управления) BackgroundWorker.
⇑
Решение
1. Запустить MS Visual Studio. Сохранить проект
Создание приложения типа Windows Forms осуществляется стандартным способом. Процесс создания нового проекта и его сохранение подробно описывается здесь.
После создания система автоматически сгенерирует новую форму с именем Form1 (рисунок 1).
Рисунок 1. Основная форма проекта
⇑
2. Размещение элементов управления на форме
На этом этапе нужно разместить следующие элементы управления на форме (рисунок 2):
- 7 элементов управления типа Label с именами label1, label2, label3, label4, label5, label6, label7;
- 4 элемента управления типа Button с именами button1, button2, button3;
- 3 элемента управления типа ListBox, которые имеют имена lisbBox1, listBox2, listBox3;
- 3 элемента управления типа ProgressBar, которые имеют имена progressBar1, progressBar2, progressBar3;
- 4 элемента управления типа BackgroundWorker с именами backgroundWorker1, backgroundWorker2, backgroundWorker3, backgroundWorker4;
- один элемент управления типа TextBox с именем textBox1.
Рисунок 2. Форма программы после размещения элементов управления
⇑
3. Настройка визуальных элементов управления формы
После размещения элементов управления нужно настроить их свойства. В окне Properties настраиваются следующие свойства элементов управления:
- в элементе управления label1 свойство 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».
После настройки, форма будет выглядеть как показано на рисунке 3.
Рисунок 3. Форма программы после настройки
⇑
4. Настройка невизуальных элементов управления типа BackgroundWorker
Элементы управления типа BackgroundWorker относятся к невизуальным элементам управления. Каждый элемент управления предназначен для реализации отдельного потока. Во всех четырех элементах управления нужно настроить свойства следующим образом:
- свойство WorkerReportProgress = True. Это означает, что разрешено отражать прогресс изменения (прогресс выполненной работы) потока;
- свойство WorkerSupportsCancellation = True. Это означает, что разрешено отменять (останавливать) выполнение потока.
⇑
5. Ввод внутренних переменных в класс формы
На этом этапе в класс формы нужно ввести дополнительные внутренние переменные. В нашем случае, вводятся следующие массивы:
- массив array типа int[]. Этот массив является массивом-оригиналом. Если в процессе сортировки будет вызвана команда отмены выполнения потоков, то все остальные недосортованные массивы получат начальное значение из этого массива;
- массив arrayBub типа int[]. Используется для представления данных при сортировке методом пузырька;
- массив arrayIns типа int[] представляет данные для сортировки методом вставки;
- массив arraySel типа int[] представляет данные для сортировки методом выбора;
- дополнительные переменные tsBubble, tsIns, tsSel типа TimeSpan, которые экономят время начала сортировки соответствующим алгоритмом;
- переменные fCancelBub, fCancelIns, fCancelSel. Эти переменные определяют, были ли вызвана команда отмены выполнения соответствующего потока (значение true).
После введенных данных сокращенный вид класса Form1 формы следующий.
public partial class Form1 : Form { // Внутренние переменные int[] array; // массив-оригинал int[] arrayBub; // массив данных после сортировки пузырьком int[] arrayIns; // массив после сортировкой вставкой int[] arraySel; // массив после сортировки выбором // Переменные, фиксирующие время начала выполнения алгоритмов TimeSpan tsBubble; // алгоритм пузырька TimeSpan tsIns; // вставка TimeSpan tsSel; // выбор bool fCancelBub; // Если true, то была нажата кнопка Stop - остановить все потоки bool fCancelIns; bool fCancelSel; ... }
⇑
6. Дополнительные внутренние методы
6.1. Метод DisplayArray(). Отображение массива в ListBox
Исходное состояние сортируемого массива отражается в следующих элементах управления:
- listBox1 — сортировка методом пузырька;
- listBox2 — сортировка вставкой;
- listBox3 — сортировка выбором.
Поскольку операция отображения массива одинакова для любого массива, то в класс формы целесообразно ввести метод, который отображает массив
public partial class Form1 : Form { ... // Внутренний метод, который отображает массив в элементе управления типа ListBox 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. Метод Active(). Активация/деактивация элементов управления
В программе нужно обеспечить правильную настройку элементов управления, которая позволит избежать трудноуловимых ошибок. Если происходит выполнение потока, генерирующего массив, то в данный момент не целесообразно запускать на выполнение сортировку тремя алгоритмами. Потому что данных для сортировки еще не достаточно (сформировался не весь массив). Во избежание подобных коллизий в программе нужно правильно устанавливать активность (неактивность) соответствующих элементов управления.
В нашем случае, активность/неактивность элементов управления устанавливается в методе Active() класса Form1. Используются элементы управления, которые предназначены для реализации и визуализации процесса сортировки.
public partial class Form1 : Form { ... // Внутренний метод активации элементов управления private void Active(bool active) { // Сделать активными/неактивными некоторые элементы управления 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. Программирование обработчиков событий визуальных элементов управления
7.1. Загрузка формы. Конструктор формы Form1
В конструктор формы целесообразно поместить код начальной инициализации элементов управления и некоторых внутренних переменных. В нашем случае текст конструктора следующий
... public Form1() { InitializeComponent(); // Очистить поле textBox1 textBox1.Text = ""; // Очистить ListBox listBox1.Items.Clear(); listBox2.Items.Clear(); listBox3.Items.Clear(); // Очистить ProgressBar progressBar1.Value = 0; progressBar2.Value = 0; progressBar3.Value = 0; // Деактивировать некоторые элементы управления Active(false); // Настроить внутренние переменные fCancelBub = false; fCancelIns = false; fCancelSel = false; } ...
⇑
7.2. Обработчик события Click кнопки button1. Команда Generate array
Для выполнения потока генерирования массива, в программе используется элемент управления backgroundWorker1. Запуск потока происходит вызовом метода RunWorkerAsync(). Чтобы избежать повторного запуска потока, который еще не закончился, используется свойство backgroundWorker1.isBusy, определяющее, выполняется ли поток на данный момент.
// Кнопка "Generate array" private void button1_Click(object sender, EventArgs e) { // Деактивировать некоторые элементы управления Active(false); // Настроить метки label5.Text = ""; label6.Text = ""; label7.Text = ""; // Запустить генерирование массива в потоке if (!backgroundWorker1.IsBusy) backgroundWorker1.RunWorkerAsync(); // сгенерировать событие DoWork }
⇑
7.3. Обработчик события Click кнопки button2. Команда Sort
При нажатии на кнопку Sort (button2) осуществляется сортировка тремя алгоритмами. Каждый алгоритм выполняется в отдельном потоке выполнения (backgroundWorker2, backgroundWorker3, backgroundWorker4).
// Кнопка "Sort" - запустить потоки на выполнение private void button2_Click(object sender, EventArgs e) { // Деактивировать кнопку генерирования массива button1.Enabled = false; // Запуск методов сортировки в потоках if (!backgroundWorker2.IsBusy) backgroundWorker2.RunWorkerAsync(); if (!backgroundWorker3.IsBusy) backgroundWorker3.RunWorkerAsync(); if (!backgroundWorker4.IsBusy) backgroundWorker4.RunWorkerAsync(); }
⇑
7.4. Обработчик события Click кнопки button3. Команда Stop
В программе существует возможность остановить процесс сортировки в потоках с помощью кнопки Stop (button3). Для программной отмены потока выполнения служит метод CancelAsync(). Этот метод имеет смысл, если свойство WorkerSupportsCancellation = true (смотрите пункт 4).
// Кнопка Stop - отменить выполнение всех потоков private void button3_Click(object sender, EventArgs e) { try { backgroundWorker2.CancelAsync(); // остановить сортировку пузырьком backgroundWorker3.CancelAsync(); // остановить сортировку вставками backgroundWorker4.CancelAsync(); // остановить сортировку выбором } catch (InvalidOperationException ex) { MessageBox.Show(ex.Message); } }
⇑
8. Программирование обработчиков событий элементов управления типа BackgroundWorker
8.1. Событие DoWork выполнения потока
В обработчике события DoWork элемента управления вставляется код, который должен выполняться в потоке. В нашем случае нужно вставить код сортировки одним из алгоритмов (вставки, выбором, пузырьком).
8.1.1. Обработчик события DoWork элемента управления backgroundWorker1. Генерирование массива
Обработчик события DoWork элемента управления backgroundWorker1 реализует выполнение потока, в котором генерируется массив случайных чисел типа int. Для вызова обработчика события DoWork нужно выполнить следующие шаги:
- активировать элемент управления backgroundWorker1;
- в окне Properties перейти во вкладку Events (события);
- в перечне событий сделать двойной клик в поле события DoWork.
Более подробно о программировании события в Microsoft Visual Studio можно прочитать здесь.
Для нашего случая, текст метода обработки события DoWork, следующий
// Выполнение потока, в котором генерируется массив чисел private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // 1. Объявление внутренних переменных Random rnd = new Random(); // 2. Получить количество элементов в массиве int n = Convert.ToInt32(textBox1.Text); // 3. Выделить память для массивов и заполнить их значениями 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); // случайное число arrayBub[i] = arraySel[i] = arrayIns[i] = array[i]; // скопировать это число // Вызвать отображение прогресса (изменения) выполнения потока try { backgroundWorker1.ReportProgress((i * 100) / n); } catch (InvalidOperationException ex) { MessageBox.Show(ex.Message); return; } } }
⇑
8.1.2. Обработчик события DoWork элемента управления backgroundWorker2. Сортировка методом пузырька
Сортировка методом пузырька запускается в обработчике события DoWork элемента управления backgroundWorker2. По образцу предыдущего пункта нужно сформировать код обработчика события DoWork.
// Сортировка методом пузырька - поток private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e) { // Сортируется массив arrayBub int x; // Инициализировать время tsBubble = new TimeSpan(DateTime.Now.Ticks); for (int i = 0; i < arrayBub.Length; i++) { Thread.Sleep(1); // дать возможность другим потокам выполняться параллельно for (int j = arrayBub.Length - 1; j > i; j--) { if (arrayBub[j - 1] > arrayBub[j]) // сортировка по возрастанию { x = arrayBub[j]; arrayBub[j] = arrayBub[j - 1]; arrayBub[j - 1] = x; } } // Отобразить изменение прогресса try { backgroundWorker2.ReportProgress((i * 100) / arrayBub.Length); } catch(InvalidOperationException ex) { MessageBox.Show(ex.Message); return; } // Проверка, был ли остановлен поток if (backgroundWorker2.CancellationPending) { fCancelBub = true; break; } } }
⇑
8.1.3. Обработчик события DoWork элемента управления backgroundWorker3. Сортировка вставками
Сортировка методом вставок реализуется в методе обработки события DoWork элемента управления backgroundWorker3.
// Сортировка вставками private void backgroundWorker3_DoWork(object sender, DoWorkEventArgs e) { // 1. Объявить внутренние переменные int x, i, j; // Инициализировать время tsIns = new TimeSpan(DateTime.Now.Ticks); // 2. Цикл сортировки for (i = 0; i < arrayIns.Length; i++) { // дать возможность другим потокам выполняться параллельно Thread.Sleep(1); x = arrayIns[i]; // Поиск места элемента в последовательности for (j = i - 1; j >= 0 && arrayIns[j] > x; j--) arrayIns[j + 1] = arrayIns[j]; // сдвинуть элемент вправо arrayIns[j + 1] = x; // Отобразить изменение прогресса try { backgroundWorker3.ReportProgress((i * 100) / arrayIns.Length); } catch (InvalidOperationException ex) { MessageBox.Show(ex.Message); return; } // Проверка, был ли остановлен поток if (backgroundWorker3.CancellationPending) { fCancelIns = true; break; } } }
⇑
8.1.4. Обработчик события DoWork элемента управления backgroundWorker4. Сортировка выбором
Сортировка выбором выполняется в потоке, которому соответствует обработчик события DoWork элемента управления backgroundWorker4.
// Сортировка выбором private void backgroundWorker4_DoWork(object sender, DoWorkEventArgs e) { // 1. Объявить переменные int i, j, k; int x; // 2. Установить начальное время tsSel = new TimeSpan(DateTime.Now.Ticks); // 3. Цикл сортировки выбором for (i = 0; i < arraySel.Length; i++) { // дать возможность другим потокам выполняться параллельно Thread.Sleep(1); k = i; // поиск наименьшего элемента x = arraySel[i]; for (j = i + 1; j < arraySel.Length; j++) if (arraySel[j] < x) { k = j; // k - индекс наименьшего элемента x = arraySel[j]; } // поменять местами наименьший элемент с arraySel[i] arraySel[k] = arraySel[i]; arraySel[i] = x; // Отобразить изменение прогресса try { backgroundWorker4.ReportProgress((i * 100) / arraySel.Length); } catch (InvalidOperationException ex) { MessageBox.Show(ex.Message); return; } // Проверка, был ли остановлен поток if (backgroundWorker4.CancellationPending) { fCancelSel = true; break; } } }
⇑
8.2. Событие ProgressChanged. Изменение прогресса выполнения потока
В обработчике события ProgressChanged вставляется код, который должен отражать прогресс проделанной на данный момент работы. В нашем случае прогресс проделанной работы отображается в элементах управления типа ProgressBar. Процент выполненной работы отображается в элементах управления типа Label.
⇑
8.2.1. Обработчик события ProgressChanged элемента управления backgroundWorker1. Отображение прогресса генерирования массива
Более подробно о программировании события в Microsoft Visual Studio описывается здесь. В нашем случае, текст обработчика события ProgressChanged элемента управления backgroundWorker1 имеет вид:
// Изменение (прогресс) выполненной работы в потоке генерирования массива private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { // Отобразить изменение в тексте кнопки "Generate array" button1.Text = "Generate array " + e.ProgressPercentage.ToString() + "%"; }
⇑
8.2.2. Обработчик события ProgressChanged элемента управления backgroundWorker2. Отображение прогресса при сортировке пузырьком
По образцу предыдущего обработчика формируется прогресс и для backgroundWorker2. Этот прогресс отражает выполненную часть сортировки пузырьком.
// Изменение прогресса в методе сортировки пузырьком private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e) { label5.Text = Convert.ToString(e.ProgressPercentage) + " %"; progressBar1.Value = e.ProgressPercentage; }
⇑
8.2.3. Обработчик события ProgressChanged элемента управления backgroundWorker3. Отображение прогресса при сортировке вставками
Для метода сортировки вставками обработчик события ProgresChanged имеет вид
// Прогресс для метода сортировки вставками private void backgroundWorker3_ProgressChanged(object sender, ProgressChangedEventArgs e) { label6.Text = Convert.ToString(e.ProgressPercentage) + " %"; progressBar2.Value = e.ProgressPercentage; }
⇑
8.2.4. Обработчик события ProgressChanged элемента управления backgroundWorker3. Отображение прогресса в методе сортировки выбором
Для сортировки выбором обработчик события ProgressChanged следующий
// Изменение прогресса для алгоритма сортировки выбором private void backgroundWorker4_ProgressChanged(object sender, ProgressChangedEventArgs e) { label7.Text = Convert.ToString(e.ProgressPercentage) + " %"; progressBar3.Value = e.ProgressPercentage; }
⇑
8.3. Событие RunWorkerCompleted. Остановка выполнения потока
Событие RunWorkerCompleted элемента управления BackgroundWorker вызывается после завершения выполнения потока. В этом событии целесообразно вписывать код завершающих операций, которые должны быть выполнены после завершения потока.
8.3.1. Обработчик события RunWorkerCompleted элемента управления backgroundWorker1
// Действия после завершения потока, генерирующего массив чисел private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // После того, как массив сгенерирован, сделать соответствующие настройки button1.Text = "Generate array"; // Сделать активными видимые элементы управления Active(true); // Отобразить массив-оригинал в элементах управления типа ListBox DisplayArray(array, listBox1); DisplayArray(array, listBox2); DisplayArray(array, listBox3); }
⇑
8.3.2. Обработчик события RunWorkerCompleted элемента управления backgroundWorker2. Завершение потока сортировки пузырьком
// Завершение сортировки методом пузырька - выполнить конечные операции private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // Если была отмена сортировки if (fCancelBub) { // Настроить соответственно элементы управления label5.Text = ""; // Отобразить массив-оригинал DisplayArray(array, listBox1); fCancelBub = false; } else { // Зафиксировать время и вывести его TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsBubble; label5.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds); // Отобразить отсортированный массив DisplayArray(arrayBub, listBox1); } // Настроить другие элементы управления progressBar1.Value = 0; button1.Enabled = true; }
⇑
8.3.3. Обработчик события RunWorkerCompleted элемента управления backgroundWorker3. Завершение потока выполнения при сортировке вставками
// Завершение потока сортировки вставками private void backgroundWorker3_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // Если была отмена сортировки if (fCancelIns) { // Настроить соответственно элементы управления label6.Text = ""; // Отобразить массив-оригинал DisplayArray(array, listBox2); fCancelIns = false; } else { // Зафиксировать время и вывести его TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsIns; label6.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds); // Отобразить отсортированный массив DisplayArray(arrayIns, listBox2); } // Настроить другие элементы управления progressBar2.Value = 0; button1.Enabled = true; }
⇑
8.3.4. Обработчик события RunWorkerCompleted элемента управления backgroundWorker4. Завершение потока сортировки выбором
// Завершение сортировки выбором private void backgroundWorker4_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // Если была отмена сортировки if (fCancelSel) { // Настроить соответственно элементы управления label7.Text = ""; // Отобразить массив-оригинал DisplayArray(array, listBox3); fCancelSel = false; } else { // Зафиксировать время и вывести его TimeSpan time = new TimeSpan(DateTime.Now.Ticks) - tsSel; label7.Text = String.Format("{0}.{1}.{2}.{3}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds); // Отобразить отсортированный массив DisplayArray(arraySel, listBox3); } // Настроить другие элементы управления progressBar3.Value = 0; button1.Enabled = true; }
⇑
9. Запуск программы. Тестирование
Рисунок 4. Процесс сортировки. Параллельное выполнение потоков
⇑
Связанные темы
- Элемент управления BackgroundWorker. Работа с потоками (фоновыми операциями). Обзор методов, свойств, событий
- Отображение прогресса выполненных работ. Отмена потока выполнения
⇑