Makale Özeti

Net framework içerisinde threading mekanizmasını geliştireceğiniz arayüz ile birlikte kullanabilir ve thread içerisinde çalışan kodunuzun sonuçlarını ya da aşamalarını arayüz içerisinde gösterebilirsiniz. Örneğin dosya sistemi(file system) üzerinde arama yapan bir programınızın olduğunu düşünün. Eğer multithreading kullanmaz iseniz program dosyaları ararken program arayüzünüz donacak (freeze) ve işlem bitene kadar kullanılamaz halde kalacaktır. Her ne kadar arada uygulamanızın kendisine gelen mesajları işlemesi için metotlar kullanıp bu sorunun önüne geçsenizde uygulamanız yinede tam manası ile düzgün çalışmayacaktır. İşte bu gibi durumlarda arayüz içerisinde threding mekanizması kullanmalısınız. Diğer örneklerde hatırlayacağınız gibi arayüzünüz ana thread içerisinde çalışmaya devam ederken yaratacağınız işçi threadler vasıtası ile hem aynı sürede daha fazla iş yapabilir hemde kullanıcının uygulama ile iletişimini kesintiye uğratmazsınız.

Makale

C#.NET ve Threading (Windows forms ve threading)

Windows forms ve threading

            .Net framework içerisinde threading mekanizmasını geliştireceğiniz arayüz ile birlikte kullanabilir ve thread içerisinde çalışan kodunuzun sonuçlarını ya da aşamalarını arayüz içerisinde gösterebilirsiniz. Örneğin dosya sistemi(file system) üzerinde arama yapan bir programınızın olduğunu düşünün. Eğer multithreading kullanmaz iseniz program dosyaları ararken program arayüzünüz donacak (freeze) ve işlem bitene kadar kullanılamaz halde kalacaktır. Her ne kadar arada uygulamanızın kendisine gelen mesajları işlemesi için metotlar kullanıp bu sorunun önüne geçsenizde uygulamanız yinede tam manası ile düzgün çalışmayacaktır. İşte bu gibi durumlarda arayüz içerisinde threding mekanizması kullanmalısınız. Diğer örneklerde hatırlayacağınız gibi arayüzünüz ana thread içerisinde çalışmaya devam ederken yaratacağınız işçi threadler vasıtası ile hem aynı sürede daha fazla iş yapabilir hemde kullanıcının uygulama ile iletişimini kesintiye uğratmazsınız.

Windows forms içerisinde threading için bir örnek verelim.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WinFormsThreading
{
   public class ThreadingForm : Form
  {

      private System.ComponentModel.IContainer components = null;

         private System.Windows.Forms.Button btnThreadFlag;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.ProgressBar progressBar1;
        private System.Windows.Forms.ProgressBar progressBar2;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.ProgressBar progressBar3;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.ProgressBar progressBar4;
        private System.Windows.Forms.Label label4;

        delegate void UpdateProgressBarDelegate(int listIdx, int randomNumber);
        private const int threadCount = 4;
        private object _threadLock = new object();
        private ManualResetEvent mrEvent;
        private bool _terminateThreads;
        private Thread[] _threadList;
        private bool TerminateThreads
        {
            set { lock (_threadLock) { _terminateThreads = value; } }
            get { lock (_threadLock) { return _terminateThreads; } }
        }

        public ThreadingForm()
        {
             InitializeComponent();
            _terminateThreads = false;
            mrEvent = new ManualResetEvent(false);
            btnThreadFlag.Tag = false;
            btnThreadFlag.Text = "Başlat";
            _threadList = new Thread[threadCount];
            for (int i = 0; i < threadCount; i++)
            {
                _threadList[i] = new Thread(new ParameterizedThreadStart(this.ThreadJob));
                _threadList[i].Start(i + 1);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void btnThreadFlag_Click(object sender, EventArgs e)
        {
            bool threadsActive = (bool)(sender as Button).Tag;
            if (!threadsActive)
                mrEvent.Set();
            else
                mrEvent.Reset();
            (sender as Button).Tag = !threadsActive;
            btnThreadFlag.Text = (!threadsActive ? "Durdur" : "Başlat");
        }

        private void UpdateProgressBars(int listIdx, int randomNumber)
        {
            switch (listIdx)
            {
                case 1: progressBar1.Value = randomNumber; break;
                case 2: progressBar2.Value = randomNumber; break;
                case 3: progressBar3.Value = randomNumber; break;
                case 4: progressBar4.Value = randomNumber; break;
            }
        }


        private void StopWorkerThreads()
        {
            TerminateThreads = true;
            mrEvent.Set();
            for (int i = 0; i < threadCount; i++)
            {
                if (_threadList[i].IsAlive)
                    _threadList[i].Join();
            }
        }

        private void ThreadJob(object threadIdx)
        {
            Random rnd = new Random((int)threadIdx * 10);
            int num;
            while (!TerminateThreads)
            {
                mrEvent.WaitOne();
                if (TerminateThreads)
                    break;
                num = rnd.Next(100);
                BeginInvoke(new UpdateProgressBarDelegate(this.UpdateProgressBars),
                              new object[] { (int)threadIdx, num });
                Thread.Sleep(100);
            }
        }

        private void ThreadingForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            StopWorkerThreads();
        }

        private void InitializeComponent()
       {
            this.btnThreadFlag = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            this.progressBar1 = new System.Windows.Forms.ProgressBar();
            this.progressBar2 = new System.Windows.Forms.ProgressBar();
            this.label2 = new System.Windows.Forms.Label();
            this.progressBar3 = new System.Windows.Forms.ProgressBar();
            this.label3 = new System.Windows.Forms.Label();
            this.progressBar4 = new System.Windows.Forms.ProgressBar();
            this.label4 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            //
            // btnThreadFlag
            //
            this.btnThreadFlag.Location = new System.Drawing.Point(12, 12);
            this.btnThreadFlag.Name = "btnThreadFlag";
            this.btnThreadFlag.Size = new System.Drawing.Size(136, 23);
            this.btnThreadFlag.TabIndex = 3;
            this.btnThreadFlag.Tag = "0";
            this.btnThreadFlag.Text = "Başlat";
            this.btnThreadFlag.UseVisualStyleBackColor = true;
            this.btnThreadFlag.Click += new System.EventHandler(this.btnThreadFlag_Click);
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(155, 13);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(50, 13);
            this.label1.TabIndex = 4;
            this.label1.Text = "Thread 1";
            //
            // progressBar1
            //
            this.progressBar1.Location = new System.Drawing.Point(212, 9);
            this.progressBar1.Name = "progressBar1";
            this.progressBar1.Size = new System.Drawing.Size(238, 17);
            this.progressBar1.TabIndex = 5;
            //
            // progressBar2
            //
            this.progressBar2.Location = new System.Drawing.Point(212, 32);
            this.progressBar2.Name = "progressBar2";
            this.progressBar2.Size = new System.Drawing.Size(238, 17);
            this.progressBar2.TabIndex = 7;
            //
            // label2
            //
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(155, 36);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(50, 13);
            this.label2.TabIndex = 6;
            this.label2.Text = "Thread 2";
            //
            // progressBar3
            //
            this.progressBar3.Location = new System.Drawing.Point(212, 55);
            this.progressBar3.Name = "progressBar3";
            this.progressBar3.Size = new System.Drawing.Size(238, 17);
            this.progressBar3.TabIndex = 9;
            //
            // label3
            //
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(155, 59);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(50, 13);
            this.label3.TabIndex = 8;
            this.label3.Text = "Thread 3";
            //
            // progressBar4
            //
            this.progressBar4.Location = new System.Drawing.Point(212, 78);
            this.progressBar4.Name = "progressBar4";
            this.progressBar4.Size = new System.Drawing.Size(238, 17);
            this.progressBar4.TabIndex = 11;
            //
            // label4
            //
            this.label4.AutoSize = true;
            this.label4.Location = new System.Drawing.Point(155, 82);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(50, 13);
            this.label4.TabIndex = 10;
            this.label4.Text = "Thread 4";
            //
            // ThreadingForm
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(462, 114);
            this.Controls.Add(this.progressBar4);
            this.Controls.Add(this.label4);
            this.Controls.Add(this.progressBar3);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.progressBar2);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.progressBar1);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.btnThreadFlag);
            this.Name = "ThreadingForm";
            this.Text = "WinFormsThreading";
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ThreadingForm_FormClosing);
            this.ResumeLayout(false);
           this.PerformLayout();

       }

      [STAThread]
      static void Main()
     {
           Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new ThreadingForm());
     }

  }
}

 

Programı çalıştırdığımızda karşımıza çıkacak ekran

igd

Örnek program açılırken ilk etapta ekranda görülen dört adet progressbar için dört ayrı thread ve ilgili threadlerin kontrolü içinse “ManualResetEvent” tipinde bir event nesnesi yaratacaktır. Başlat düğmesine tıklandığında düğme başlığı “Durdur” olarak değişecek ve event nesnesinin durumunu “SET” olarak değiştirecektir. Bu durumda threadler beklemeden çıkacak ve progressbar’ların değerlerini keyfi olarak değiştireceklerdir. Tekrar “Durdur” düğmesine tıklandığında ise eventRESET” durumuna geçecek ve ilgili threadler beklemeye geçecektir.

            Windows forms üzerinde threading uygularken dikkat etmeniz gereken bir husus var. İşçi thread arkaplanda çalışırken aynı zamanda örnek uygulamamızdaki gibi arayüzdeki bir elementin(widget) herhangi bir değerini değiştirecektir. Uygulamamızda işçi thread ana thread içerisinde çalışan arayüzün ilgili progressbar’in değerini değiştirmekte idi. Ama bu işlem sırasında “data race” e düşmemek için Windows forms’ûn senkronizasyon metodlarını kullanmamız gerekiyor. İşçi thread içerisinden arayüze yapacağımız herhangi bir müdahele programın tutarlılığını kaybettirecek hatta deadlock’a kadar götürecektir. Peki bu senkronizasyonu nasıl sağlarız. Örnek uygulamamızdan bir parçaya bakalım.

 

        //Invoke ile çalıştıracağımız metotlar için bir delege tanımlıyoruz.
        delegate void UpdateProgressBarDelegate(int listIdx, int randomNumber);
        ………………………………………………………………………………………….
        ………………………………………………………………………………………….
        ………………………………………………………………………………………….
        //Ana thread içerisinde çalışan arayüz elementlerine ulaşan ve onlar
      //üzerinde işlem yapan kod parçasını daha önce tanımladığımız delege tipinde
     //olmak üzere bir metot içerisine yazıyoruz.
        private void UpdateProgressBars(int listIdx, int randomNumber)
        {
            switch (listIdx)
            {
                case 1: progressBar1.Value = randomNumber; break;
                case 2: progressBar2.Value = randomNumber; break;
                case 3: progressBar3.Value = randomNumber; break;
                case 4: progressBar4.Value = randomNumber; break;
            }
        }
        ………………………………………………………………………………………….
        ………………………………………………………………………………………….
        ………………………………………………………………………………………….
        private void ThreadJob(object threadIdx)
        {
            Random rnd = new Random((int)threadIdx * 10);
            int num;
            while (!TerminateThreads)
            {
                mrEvent.WaitOne();
                if (TerminateThreads)
                    break;
                num = rnd.Next(100);
                //Arayüz elementleri için işlem yapmamız gerekiyor. Bu durumda
              //ana sınıfın(class) BeginInvoke metotu ile arayüz elementleri üzerinde işlem
            //yapacak kod bloğunu parametrelerinide vererek ana sınıf içerisinde
            //çalıştırıyoruz.
                BeginInvoke(new UpdateProgressBarDelegate(this.UpdateProgressBars),
                              new object[] { (int)threadIdx, num });
                Thread.Sleep(100);
            }
        }

 

Detayda anlaşılacağı üzere, aslında tek yapılan şey arayüz elementlerine ulaşan kod parçalarını bir metot içerisine almak ve daha sonra bu metotu parametreleri ile birlikte BeginInvoke() sayesinde ana thread’in içerisinde çalıştırmak. Bu sayede data race ya da deadlock probleminden kurtulmuş oluyoruz.

Ayrıca .net framework içerisinde bize sunulan birkaç fonksiyon daha var.

public bool InvokeRequired() :  Üzerinde işlem yapmak istediğiniz element(control)’in InvokeRequired() fonksiyonu o element’in bir metodunu çağıracak kod parçasının aynı thread’demi ya da başka bir thread’demi olduğunu tespit etmek için kullanılır. Eğer geriye “false” dönerse element üzerinde direk işlem yapabilirsiniz fakat geriye “true” dönerse Invoke() metodu ile birlikte biraz önceki kullanımı uygulamanız gerekecektir.
Invoke(…) : Belirttiğiniz delegeyi Invoke() metodunu çağırdığınız elementin içinde içinde çalıştığı thread içerisinde çalıştıracaktır. Bu durumda element üzerinde işlem yapan kod parçaları elementin ait olduğu thread’de çalışacağından tutarsizlik gibi bir sorun oluşmayacaktır.
BeginInvoke(…) : Invoke() da olduğu gibi parametre olarak verilen delegeyi asenkron olarak BeginInvoke() fonksiyonunu çağırdığınız element’in içerisinde çalıştırır.