This article is currently in the process of being translated into Turkish (~38% done).
Cancelling the BackgroundWorker
Bir önceki yazımızda gördüğümüz üzere çoklu kod çevrimi (multi-thread) uygulamamız uzun zaman gerektiren işlemleri yaparken proses ilerlemesini kullanıcı ara yüzü klitlenmeden gösterebilme özelliğini uygulamamıza eklemişti.
Tüm işlemleri UI kod çevrimi içerisinde yapmanız halinde karşılaşacağınız bir diğer problem ise çalışan uygulamayı durdurmanın hiç bir yolunun olmamasıdır. Bunun nedeni UI kod çevrimi uzun süren işlemleri yaparken Vazgeç yada Esc tuşlarına ne kadar çok bastığınızdan bağımsız olarak hiç bir giriş işlenemez.
Şanslıyız ki BackgroundWorker proses ve prosesi durdurma (cancellation) işlemleri için desteği kolayca sağlayacak şekilde oluşturulmuştur. Proses kısmına ayrıntılı olarak bir önceki bölümde göz attığımızdan bu bölümde sadece prosesi durdurma kısmına bakacağız. Şimdi doğrudan örneğimize geçelim.
<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 kısmı son derece sade bir şekilde düzenlenmiştir. Güncel durumu göstermek üzere bir Label ile süreci başlatıp durduracak iki adet buton. Hepsi bu...
Kod dosyasına ise bir BackgroundWorker örneğinin oluşturulması ile başlıyoruz. WorkerSupportsCancellation ve WorkerReportsProgress özelliklerine özellikle dikkatinizi çekmek isterim. Bu özelliklerin ikisi de doğru (true) yapılmalıdır. Aksi halde bu özellikleri kullanmaya çalıştığımızda ortaya bir istisna (exception) çıkar.
The cancel button simply calls the CancelAsync() method - this will signal to the worker that the user would like to cancel the running process by setting the CancellationPending property to true, but that is all you can do from the UI thread - the rest will have to be done from inside the DoWork event.
In the DoWork event, we count to 100 with a 250 millisecond delay on each iteration. This gives us a nice and lengthy task, for which we can report progress and still have time to hit that Cancel button.
Notice how I check the CancellationPending property on each iteration - if the worker is cancelled, this property will be true and I will have the opportunity to jump out of the loop. I set the Cancel property on the event arguments, to signal that the process was cancelled - this value is used in the RunWorkerCompleted event to see if the worker actually completed or if it was cancelled.
In the RunWorkerCompleted event, I simply check if the worker was cancelled or not and then update the status label accordingly.
Summary
So to sum up, adding cancellation support to your BackgroundWorker is easy - just make sure that you set WorkerSupportsCancellation property to true and check the CancellationPending property while performing the work. Then, when you want to cancel the task, you just have to call the CancelAsync() method on the worker and you're done.