Фоновые процессы в .NET на C#
Что такое фоновая обработка?
Как разработчики, мы часто сталкиваемся со случаями, когда нам приходится выполнять длительные или ресурсоемкие операции, такие как получение данных с сервера, выполнение сложных вычислений или обработка больших файлов. Эти операции влияют на производительность приложения и задерживают основной поток работы приложения. Решение состоит в том, чтобы передать эти рабочие нагрузки процессу, который может запускать их в фоновом режиме и возвращать результаты по завершении процесса. Это особенно важно для приложений, которым необходимо реагировать на взаимодействие с пользователем и обеспечивать бесперебойную работу пользователей.
В данном материале, мы обсудим фоновые процессы .NET и то, как их использовать в приложении Windows Forms с использованием C#
Фоновые процессы (Background Workers)
В .NET класс BackgroundWorker предоставляет упрощенный способ выполнения фоновой обработки в приложениях Windows Forms. Его основная цель - упростить процесс выполнения трудоемких операций в отдельном потоке, сохраняя при этом адаптивность потока пользовательского интерфейса (UI).
Многопоточность необходима для создания адаптивных приложений, поскольку она позволяет выполнять задачи одновременно, предотвращает зависание пользовательского интерфейса во время длительных операций и максимизирует использование ресурсов. Однако многопоточность сопряжена с такими проблемами, как синхронизация, состояние гонки (конкуренция) и потокобезопасность, которыми необходимо тщательно управлять, чтобы избежать ошибок и неожиданного поведения. Такие инструменты, как класс BackgroundWorker в C#, предоставляют структурированный способ управления многопоточностью и создания мощных и удобных в использовании приложений.
Как BackgroundWorker обеспечивает фоновую обработку?
Он основан на событийной модели и предоставляет три основных события, которые вам необходимо обработать:
- DoWork: здесь вы определяете код, который будет выполняться в фоновом режиме, и он будет выполняться в фоновом потоке.
- RunWorkerCompleted: запускается по завершении фоновой работы. Он выполняется в потоке пользовательского интерфейса и позволяет обрабатывать результаты фоновой задачи и соответствующим образом обновлять пользовательский интерфейс.
- ProgressChanged: позволяет вам обновлять элементы пользовательского интерфейса информацией о ходе выполнения во время выполнения фонового процесса.
Когда вы запускаете фоновую задачу с помощью BackgroundWorker, она автоматически создает новый поток для выполнения кода, определенного в событии DoWork. Этот отдельный поток гарантирует, что основной поток пользовательского интерфейса остается отзывчивым, даже если фоновая задача занимает значительное количество времени. Одной из ключевых особенностей BackgroundWorker является его способность взаимодействовать с потоком пользовательского интерфейса безопасным и контролируемым образом. Пока событие DoWork выполняется в фоновом потоке, вы можете использовать метод ReportProgress для отправки обновлений о ходе выполнения в поток пользовательского интерфейса. Затем в потоке пользовательского интерфейса запускается событие ProgressChanged, позволяющее обновлять элементы пользовательского интерфейса с информацией о ходе выполнения.
Работа с BackgroundWorker
Теперь давайте посмотрим на все это в действии на примере. Нам потребуется:
- Visual Studio 2022
- .NET 6
- .NET Desktop Development (устанавливается инсталлятором Visual Studio)
Начнем с создания нового приложения Windows Forms.
Добавьте в конструктор следующие элементы управления, включая элемент управления BackgroundWorker:
Обратите внимание, что элемент управления BackgroundWorker не отображается в форме, потому что мы не взаимодействуем с ним через пользовательский интерфейс. Вот код конструктора:
namespace BackgroundWorkerExample {
partial class Form1 {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
processButton = new Button();
cancelButton = new Button();
progressBar = new ProgressBar();
progressLabel = new Label();
backgroundWorker = new System.ComponentModel.BackgroundWorker();
randomNumberButton = new Button();
randomNumberLabel = new Label();
SuspendLayout();
//
// processButton
//
processButton.Location = new Point(12, 12);
processButton.Name = "processButton";
processButton.Size = new Size(138, 29);
processButton.TabIndex = 0;
processButton.Text = "Process";
processButton.UseVisualStyleBackColor = true;
processButton.Click += processButton_Click;
//
// cancelButton
//
cancelButton.Location = new Point(174, 12);
cancelButton.Name = "cancelButton";
cancelButton.Size = new Size(139, 29);
cancelButton.TabIndex = 1;
cancelButton.Text = "Cancel";
cancelButton.UseVisualStyleBackColor = true;
cancelButton.Click += cancelButton_Click;
//
// progressBar
//
progressBar.Location = new Point(12, 53);
progressBar.Name = "progressBar";
progressBar.Size = new Size(301, 29);
progressBar.TabIndex = 2;
//
// progressLabel
//
progressLabel.AutoSize = true;
progressLabel.Location = new Point(12, 96);
progressLabel.Name = "progressLabel";
progressLabel.Size = new Size(29, 20);
progressLabel.TabIndex = 3;
progressLabel.Text = "0%";
//
// backgroundWorker
//
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
//
// randomNumberButton
//
randomNumberButton.Location = new Point(12, 138);
randomNumberButton.Name = "randomNumberButton";
randomNumberButton.Size = new Size(138, 29);
randomNumberButton.TabIndex = 4;
randomNumberButton.Text = "Random Number";
randomNumberButton.UseVisualStyleBackColor = true;
randomNumberButton.Click += button1_Click;
//
// randomNumberLabel
//
randomNumberLabel.AutoSize = true;
randomNumberLabel.Location = new Point(203, 142);
randomNumberLabel.Name = "randomNumberLabel";
randomNumberLabel.Size = new Size(17, 20);
randomNumberLabel.TabIndex = 5;
randomNumberLabel.Text = "0";
//
// Form1
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(330, 187);
Controls.Add(randomNumberLabel);
Controls.Add(randomNumberButton);
Controls.Add(progressLabel);
Controls.Add(progressBar);
Controls.Add(cancelButton);
Controls.Add(processButton);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
PerformLayout();
}
#endregion
private Button processButton;
private Button cancelButton;
private ProgressBar progressBar;
private Label progressLabel;
private System.ComponentModel.BackgroundWorker backgroundWorker;
private Button randomNumberButton;
private Label randomNumberLabel;
}
}
Теперь обновите свойства WorkerReportProgress и WorkerSupportsCancellation элемента управления BackgroundWorker на значение true. Они нужны нам для предоставления пользовательскому интерфейсу данных о ходе выполнения, а также для отмены фонового процесса при необходимости.
Перейдите на вкладку события в свойствах BackgroundWorker и щелкните по каждому из 3 событий, чтобы сгенерировать обработчики событий:
Это приведет к созданию пустых обработчиков событий, для которых мы предоставим реализацию.
Чтобы использовать BackgroundWorker, вы просто указываете ему, какой нагруженный метод воркера следует выполнить в фоновом режиме в обработчике событий DoWork.
private void backgroundWorker_DoWork(object sender,
System.ComponentModel.DoWorkEventArgs e) {
for (int i = 0; i <= 100; i++) {
Thread.Sleep(100);
backgroundWorker.ReportProgress(i);
if (backgroundWorker.CancellationPending) {
e.Cancel = true;
backgroundWorker.ReportProgress(0);
return;
}
}
e.Result = "Completed";
}
Событие запускает цикл for и задерживается на 100 миллисекунд на каждой итерации. Это имитирует трудоемкую операцию, такую как HTTP-запрос на загрузку огромного файла. Он также информирует основной поток о ходе выполнения операции с помощью метода ReportProgress. Если была инициирована отмена, будет установлено CancellationProperty в BackgroundWorker, которое может быть обработано соответствующим образом. Если цикл for завершается без какой-либо отмены, Result в DoWorkEventArgs обновляется значением. Это событие будет передано в аргументы обработчика событий RunWorkerCompleted как RunWorkerCompletedEventArgs.
Теперь мы можем запустить событие DoWork, вызвав метод RunWorkerAsync внутри обработчика событий processButton_Click.
private void processButton_Click(object sender, EventArgs e) {
if (!backgroundWorker.IsBusy) {
backgroundWorker.RunWorkerAsync();
}
}
Пока рабочий метод выполняется в фоновом режиме, основной поток может безопасно обновляться в соответствии с ходом выполнения с помощью обработчика событий ProgressChanged.
private void backgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) {
progressBar.Value = e.ProgressPercentage;
progressLabel.Text = e.ProgressPercentage.ToString() + "%";
}
Вызывающий поток продолжает работать в обычном режиме, в то время как рабочий метод выполняется асинхронно. Когда метод завершается, BackgroundWorker предупреждает вызывающий поток, запуская событие RunWorkerCompleted, которое необязательно содержит результаты операции.
private void backgroundWorker_RunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
progressLabel.Text = "Processing cancelled";
} else if (e.Error is not null) {
progressLabel.Text = e.Error.Message;
} else {
progressLabel.Text = e.Result?.ToString();
}
}
Чтобы отменить фоновую обработку, пока она еще выполняется, вызовите метод CancelAsync.
private void cancelButton_Click(object sender, EventArgs e) {
if (backgroundWorker.IsBusy) {
backgroundWorker.CancelAsync();
}
}
Чтобы продемонстрировать, насколько мощным является BackgroundWorker, давайте добавим некоторый код для генерации случайного числа, которое будет сгенерировано в основном потоке во время выполнения фоновой обработки.
private void randomNumberButton_Click(object sender, EventArgs e) {
randomNumberLabel.Text = new Random().Next(0, 1000).ToString();
}
Обработка исключений
Событие DoWork может вызывать исключения, которые автоматически перехватываются и помещаются в свойство Error события RunWorkerCompletedEventArgs, которое передается в аргументы обработчика событий RunWorkerCompleted. Вы можете использовать блок try-catch для обработки исключения, но имейте в виду, что если исключение не генерируется в блоке catch, RunWorkerCompletedEventArgs.Error будет равен null.
Обновите обработчик событий DoWork следующим образом:
private void backgroundWorker_DoWork(object sender,
System.ComponentModel.DoWorkEventArgs e) {
try {
for (int i = 0; i <= 100; i++) {
Thread.Sleep(100);
backgroundWorker.ReportProgress(i);
if (backgroundWorker.CancellationPending) {
e.Cancel = true;
backgroundWorker.ReportProgress(0);
return;
}
throw new InvalidOperationException("Random exception.");
}
} catch {
throw;
}
e.Result = "Completed";
}
Попробуйте это с попыткой перехватить ошибку или без нее, а также с выбросом исключения и наоборот.
Заключение
Мы можем использовать компонент BackgroundWorker для создания адаптивных приложений Windows Forms, выделяя трудоемкие задачи в отдельный поток, делая пользовательский интерфейс более удобным для пользователя и избегая зависания или отсутствия ответа. BackgroundWorker обеспечивает абстрагирование от многопоточности в .NET и упрощает создание адаптивных приложений, не беспокоясь об управлении потоками.
Хотя BackgroundWorker является ценным инструментом, в современных версиях C# и .NET доступны более новые опции, такие как библиотека параллельных задач (TPL) и шаблоны async/await. Эти альтернативы предоставляют более продвинутые и гибкие подходы к управлению асинхронными задачами.
Комментарии
Для того чтобы оставить свое мнение, необходимо зарегистрироваться на сайте