C#. Windows Forms. Разработка программы демонстрации работы с потоками выполнения в приложениях типа Windows Form. Элемент управления BackgroundWorker.




Разработка программы демонстрации работы с потоками выполнения в приложениях типа Windows Forms. Элемент управления BackgroundWorker. Сортировка массива алгоритмами вставки, пузырьком, выбором

В данной теме подробно описывается процесс создания Windows Forms приложения, которое реализует сортировку массива чисел в трех параллельных потоках. Тема будет полезна начинающим программистам при изучении особенностей работы с потоками выполнения, основанных на компоненте (классе) BackgroundWorker. Демонстрационное приложение создается в системе Microsoft Visual Studio 2019.

Перед изучением данной темы рекомендуется ознакомиться со следующими темами:


Содержание


Поиск на других ресурсах:

Условие задачи

Разработать приложение типа Windows Forms Application. В приложении реализовать следующие операции:

  • генерирование массива случайных чисел указанной размерности;
  • сортировку сгенерированного массива тремя алгоритмами: вставки, пузырьком, выбором;
  • визуализацию процесса сортировки каждым алгоритмом;
  • вывод информации о продолжительности сортировки каждым алгоритмом с целью сравнения.

Генерирование массива и его сортировку реализовать в различных потоках. Для представления отдельного потока использовать возможности класса (элемента управления) BackgroundWorker.

 

Решение

1. Запустить MS Visual Studio. Сохранить проект

Создание приложения типа Windows Forms осуществляется стандартным способом. Процесс создания нового проекта и его сохранение подробно описывается здесь.

После создания система автоматически сгенерирует новую форму с именем Form1 (рисунок 1).

C#. Windows Forms. Создание приложения с использованием потоков

Рисунок 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.

 

C#. Windows Forms. Создание приложения сортировки в потоках разными алгоритмами

Рисунок 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.

C#. Windows Form. Форма программы демонстрации работы потоковРисунок 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. Запуск программы. Тестирование

 

C#. Windows Forms. BackgroundWorker. Программа сортировки массива в разных потоках

Рисунок 4. Процесс сортировки. Параллельное выполнение потоков

 


Связанные темы