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. BackgroundWorker. Основна форма програми

Рисунок 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 Forms. Налаштування програми демонстрації сортування в потоках

Рисунок 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. Відображення виконаного прогресу при сортуванні вставками

Для методу сортування вставками обробник події ProgressChanged має вигляд

// Прогрес для методу вставки
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. Процес сортування. Паралельне виконання потоків

 


Зв’язані теми