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. Процесс сортировки. Параллельное выполнение потоков

  ⇑


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

  ⇑