Merhaba arkadaşlar..
Bu yazımızda “BackgroundWorker” kontrolünü inceleyeceğiz. Windows uygulamalarında bazı işlerin ilerlemesi çok zaman alır. Örneğin ağ üzerinde bir dosya transferi yapan bir program yaptınız. Transfer olayı 5 dakika sürüyor diyelim. Program bu işlemi yaparken de kullanıcının etkisine karşı bir tepki verebilmelidir. Bizim yapmamız gereken iş, transfer işlemi yürütülürken, programın kullanıcıya tepki vermesini sağlamak için, arka planda yeni bir thread açmaktır. Thread ‘ın, “geçir(fiil), iplik(isim),en küçük yürütme birimi(isim)” gibi kelime anlamları vardır. Programlamadaki anlamı ise, işlemciye veri aktarımı yapan küçük veri yollarıdır. Bu açıklamalardan sonra  BackgroundWorker kontrolü ile ilişkisini anlatmak gerekirse, BackgroundWorker arka planda thread ‘lerle çalışır, thread ‘leri kontrol eder. Bu açtığım thread sayesinde programım kilitlenmeyecektir. Bu işi yapan komponente “BackgroundWorker” denir. Kısacası BackgroundWorker, windows uygulamalarında “multi thread” uygulama geliştirmenize olanak sağlar.

Şimdi BackgroundWorker kontrolünün özelliklerini inceleyelim:

  • WorkerReportsProgress, özelliği “true” yapılırsa, BackgroundWorker çalışırken yapılan işlemlerle ilgili, dışarıya anlık, güncel bilgi gönderir. Bu bilgi  BackgroundWorker ‘ın ProgressChanged event ‘inde yakalanır. Default değeri “false” ‘tur.
  • WorkerSupportsCancellation, true ise kullanıcı, BackgroundWorker kontrolünün arka planda yaptığı işi sonladırabilir. Default değeri “false” ‘tur. Örneğin programcı olarak ben, arka planda yapılan iş bitmeden, işlemin sonlandırılmasını istemeyebilirim. Bu özelliğin bu opsiyonu sunmasının nedeni de budur.

Bir de bu kontrolümüzün event ‘lerine bir göz atalım. 3 tane event ‘e sahiptir. Bunlar:

  • DoWork, event ‘inde BackgroundWorker ‘ın yapılacağı işlemler tanımlanır. BackgroundWorker çalıştırıldığında bu metot çalışmaya başlar. Peki BackgroundWorker ne zaman çalışır? BackgroundWorker ‘ın “RunWorkAsync()” adında bir metodu vardır.  “RunWorkAsync” metodu bir defa çağırıldığında “DoWork” event ‘i de tetiklenmiş olur ve iki iş birbirinden bağımsız olarak çalışmaya başlar. “RunWorkAsync” ‘ın, “run work asynchron” kısaltmasıdır. Kelime anlamı da “bağımsız olarak çalış” demektir.
  • ProgressChanged, BackgroundWorker ‘ın WorkerReportsProgress özelliği true ise, BackgroundWorker ‘ın yaptığı her işlem değişikliğinde bu event tetiklenir. Bu event ‘te genellikle bir “ProgressBar” kontrolü üzerinde, BackgroundWorker ‘ın işlemlerinin durumu izlenir.
  • RunWorkerComplated,  BackgroundWorker ‘ın işlemi bittiğinde bu event tetiklenir. Eğer programda ayrı thread ‘deki yapılan işin bittiğinden haberdar olmak isteniyorsa bu event kullanılır. Böylece kullanıcı dostu bir uygulama geliştirmeye olanak sağlar. Bu event, bir kere çalışır.

Hemen bir örnek üzerinde bu kontrolümüzü daha yakından tanıyalım. “IsYap” adında bir metodum olsun. Bir tane de butonum olsun. Metodum 1’den 100000’e kadar olan sayıları, formun textine yazsın ve bu sayıların toplamını çalışması bittikten sonra MessageBox ‘da göstersin. Metodumuzu butonun “Click” event ‘inde çağıralım.

private void IsYap()
        {
            long toplam = 0;
            for (int i = 0; i < 100000; i++)
            {
                toplam += i;
                this.Text = i.ToString();
            }
            MessageBox.Show(toplam.ToString());
        }
        private void btnIsYap_Click(object sender, EventArgs e)
        {
            IsYap();
        }

Programı bu haliyle çalıştırdığımda, kullanıcıya tepki vermiyor, formumu sürükleyemiyorum. Formun textinde “not responding” (yanıt vermiyor) ibaresi görmek mümkün. Bir kilitlenme söz konusu. Çünkü thread ‘im meşgul. Bu olayı bir örnekle anlamaya çalışalım.

Uygulamada “main thread” denilen bir thread vardır. Bu thread, program çalıştığında formun görsellerini cpu da işleyip ekrana getirmekten sorumludur. Biz zaman gerektiren bir iş parçacığı yollarsak bu thread ‘de bir trafik meydana gelir. Bu thread ‘de oluşan bir trafik görselliği olumsuz yönde etkiler. Çünkü bir yandan iş yapmaya çalışırken diğer yandan programın anlık görselliğini de taşımak zorundadır. Bunları aynı anda yapamaz. Aynı anda yapamaz derken arkadaşlar: Tek şeritli bir yol düşünün. Aynı anda iki araba geçebilir mi? Ancak sırayla geçebilirler. Bu örnekte thread bizim tek şeritli yolumuz, araba ise işin tamamı değil, işin bir parçası olsun. Eğer thread ‘in üzerinden iki iş aynı anda geçmeye çalışırsa, thread bunu bir işten bir parça, diğer işten bir parça alarak yapar. Böylece görsellikte bir aksaklık meydana gelir.
Bu sorun gidermek için, en az bir adet daha thread ‘e ihtiyaç duyulur. Bu thread ‘e de “cross thread” denir. Böylelikle “main thraed” im rahatlıkla görsellikle ilgilenirken, yeni açtığım thread de benim yapmak istediğimiz iş ile ilgilenir.

Projeme “ToolBox” ‘dan bir adet BackgroundWorker atalım. İşlemimi butonun “Click” event ‘inde değil de BackgroundWorker ‘ın “DoWork” event ‘inde tanımlayalım. Ve butonun “Click” event ‘inde BackgroundWorker ‘ın çalışmasını başlatalım.

private void btnIsYap_Click(object sender, EventArgs e)
        {
            //IsYap();
            backgroundWorker1.RunWorkerAsync();
        }
        private void IsYap()
        {
            long toplam = 0;
            for (int i = 0; i < 100000; i++)
            {
                toplam += i;
                this.Text = i.ToString();
            }
            MessageBox.Show(toplam.ToString());
        }
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            IsYap();
        }

Bu bir “cross-thread” hatasıdır arkadaşlar. C# uygulamalarında, uygulama yanlış yönlendirilmemesi amacıyla, “multi-threading” özelliği default olarak yasaktır. Bu özelliği kullanacağımı belirtmeliyim. Programa, ben “birden fazla thread kullanmak istiyorum” demeliyim. Bunu da ya formun “Load” event ‘inde ya da formun “constructor” ‘da (formun yapıcısında) belirtebilirim. Fakat formun yapıcısının ilk önce çalışmasından dolayı bu kontrolü formun yapıcısında, formun komponentleri yüklenmeden yapmam daha mantıklıdır.

 public Form1()
        {
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
        }

CheckForIllegalCrossThreadCalls = false; diyerek, “yasal olmayan thread çağrılarını kontrol etme” demiş bulunuyorum.

Şimdi programın sorunsuz bir şekilde çalışacaktır. BackgroundWorker ‘ın  yaptığı işi bir “ProgressBar” üzerinde görmek istersek, formumuza bir ProgressBar kontrolü atalım, WorkerReportsProgress özelliğini “true” yapalım. BackgroundWorker ‘ın  yaptığı işteki her değişiklikte “ProgressChanged” event ‘ini tetikletmek için “IsYap” metodunda  backgroundWorker1.ReportProgress metodunu çalıştıralım. Bu metot, int tipinde bir değer ister, bu da işin yüzde kaçının bittiğini gösterir. Bu yüzden buraya girilen değer en fazla 100 olabilir. i ‘yi 1000 ‘e bölmemizin sebebi de budur. Bu sorunu ProgessBar ‘ın özelliklerinden en büyük değerini “Maximum= 100000” yaparak da çözebilirdik. Son olarak “RunWorkerCompleted” event ‘inde de “sonuc” değişkenini göstermesini isteyelim. Şimdi programımızın son haline bir göz atalım.

 public Form1()
        {
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
        }
        private void btnIsYap_Click(object sender, EventArgs e)
        {
            //IsYap();
            backgroundWorker1.RunWorkerAsync();
        }

        long toplam = 0;
        private void IsYap()
        {
            for (int i = 0; i <= 100000; i++)
            {
                toplam += i;
                this.Text = i.ToString();
                backgroundWorker1.ReportProgress(i);
            }
        }
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            IsYap();
        }
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show(toplam.ToString());
        }

Şimdi programımız kullanıcıya çok rahat tepki verebiliyor. (Formumu sürükleyebiliyorum, butonum aktif, TextBox atsam değer girebilirim vs..) Programın çalışmasını daha yavaş incelemek istiyorsak for döngümüzün içerisine System.Threading.Thread.Sleep(100); kodunu ekleyebiliriz. Thread sınıfının “Sleep” mototu mili saniye cinsinden bir parametre ister ve programı akışını, her döngüde belirttiğim mili saniye değerinde uyutur.

Bunun gibi ve bu uygulamadan daha karmaşık uygulamalarda çok işimize yarayabilecek bir kontrolü inceledik arkadaşlar. Umarım yardımcı olabilmişimdir.

Başka yazılarda görüşmek dileğiyle, hoşçakalın..