This article has been localized into Ukrainian by the community.
Скасування роботи фонового робітника
Як ми бачили у попередній статті, багатопоточність має чимало переваг. Зокрема завдяки їй, ваша програма не зависай при виконанні довготривалих завдань, крім того, вона дозволяє показувати прогрес виконання завдання.
Іншою серйозною проблемою, що виникає при виконанні ресурсозатратних завдань в основному потоці, є те, що користувач не може їх скасувати. Адже поки цей потік зайнятий, не працює ввід, а отже, як би сильно користувач не натискав кнопку "Скасувати", нічого не станеться.
На щастя, завдяки фоновому робітнику (надалі BackgroundWorker) можна з легкістю скасовувати процеси. Попередній розділ був про виконання завдань у BackgroundWorker, а цей буде про їх скасування. Почнімо з прикладу:
<Window x:Class="WpfTutorialSamples.Misc.BackgroundWorkerCancellationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BackgroundWorkerCancellationSample" Height="120" Width="200">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Name="lblStatus" HorizontalAlignment="Center" Margin="0,10" FontWeight="Bold">Not running...</TextBlock>
<WrapPanel>
<Button Name="btnStart" Width="60" Margin="10,0" Click="btnStart_Click">Start</Button>
<Button Name="btnCancel" Width="60" Click="btnCancel_Click">Cancel</Button>
</WrapPanel>
</StackPanel>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
namespace WpfTutorialSamples.Misc
{
public partial class BackgroundWorkerCancellationSample : Window
{
private BackgroundWorker worker = null;
public BackgroundWorkerCancellationSample()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
worker.CancelAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i <= 100; i++)
{
if(worker.CancellationPending == true)
{
e.Cancel = true;
return;
}
worker.ReportProgress(i);
System.Threading.Thread.Sleep(250);
}
e.Result = 42;
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblStatus.Text = "Working... (" + e.ProgressPercentage + "%)";
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
lblStatus.Foreground = Brushes.Red;
lblStatus.Text = "Cancelled by user...";
}
else
{
lblStatus.Foreground = Brushes.Green;
lblStatus.Text = "Done... Calc result: " + e.Result;
}
}
}
}
XAML розмітка тут дуже проста: напис, що показує статус виконання завдання, та дві кнопки для початку та скасування виконання.
C# частина починається зі створення екземпляра класу BackgroundWorker. Зверніть увагу на властивості WorkerSupportsCancellation та WorkerReportsProgress, яким ми задали значення true. В іншому разі виникнула б помилка при спробі використання відповідних функцій.
Кнопка "Скасувати" просто викликає метод CancelAsync(), що повідомляє BackgroundWorker про те, що користувач хоче скасувати поточний процес. Для цього метод задає CancellationPending властивості значення true. Це все, що ви можете робити в головному потоці, за решту відповідає подія DoWork.
В події DoWork ми рахуємо до ста, роблячи затримку 250 мілісекунд при кожній ітерації. Завдяки цьому ми отримуємо чудове довге завдання, при виконанні якого можна легко повідомити прогрес, а також достатньо часу для скасування.
Зверніть увагу на те, як я перевіряю значення властивості CancellationPending під час кожної ітерації. При скасуванні завдання вона отримає значення true, а я завершу цикл. Я встановив значення властивості Cancel, щоб повідомити про скасування процесу. Це значення використовується в події RunWorkerCompleted для того, щоб дізнатися було завдання виконане, чи завершене.
В події RunWorkerCompletedя просто перевіряю, чи було скасовано виконання завдання, після чого оновлюю підпис.
Підсумок
Отже, до вашого BackgroundWorker можна з легкістю додати можливість скасування. Просто встановіть значення true для властивості WorkerSupportsCancellation та перевіряйте значення властивості CancellationPending, виконуючи роботу. Тож, якщо вам потрібно скасувати завдання, просто викличте метод CancelAsync().