Makale Özeti

Bu makalede sizlere thread’lerden ve Thread nesnesinden bahsedeceğim. Thread, yani kelime anlamı olarak “kanal”. Peki nedir bu kanallar? Thread’ler, process’ler içinde bulunan, aynı zaman diliminde birden fazla iş yapılmasına olanak tanıyan yapılardır.

Makale

Merhaba arkadaşlar. Bu makalede sizlere thread’lerden bahsedeceğim. Thread, yani kelime anlamı olarak “kanal”. Peki nedir bu kanallar? Thread’ler, process’ler içinde bulunan, aynı zaman diliminde birden fazla iş yapılmasına olanak tanıyan yapılardır. Yani thread ile siz, bir chat programında yazı yazarken, müzik dinleyebilirsiniz. Burada chat programı için ayrı, müziğin çalması için ayrı bir thread arka planda çalışmaktadır.

Buraya kadar thread ile ilgili kafanızda bir şeyler oluşmuştur. Ancak kötü bir haberim var. Threadler gerçekten işlerini aynı anda yapmıyorlar. İşlemci threadlerimize belli bir zaman dilimi veriyor – burada milisaniyelik zamanları düşünelim- threadler arasında çok hızlı geçişler yapıyor ve biz bunu algılamıyoruz. Bu noktada belirtmeliyim ki threadlerin çalışması işlemciden işlemciye bile değişebilir. Birçok algoritması vardır. Örneğin hepsine aynı zaman dilimini ayırılabilir, önceliklerine göre zaman verilebilir, en kısa sürede bitene göre ya da bitimine en az süre kalana göre zamanlama ayarlanabilir. Bu bizi pek ilgilendirmiyor ama.

Thread’lerin “ready”,”blocked”,”running” ve “terminated” durumları vardır. Running moda olan thread aktiftir ve işlemciyi kullanır. Blocked moddaki thread ise beklemededir.(Örneğin bir girdi[input] bekliyordur.) Ready moddaki thread ise çalışma sırasının kendisine gelmesini bekliyordur. Terminated moddaki ise çalışmasını bitirmiştir.

Thread ile ilgili mantıksal anlatım yeterli olmuştur sanırım. Artık C# ile kullanmaya başlayalım.

Thread ile çalışmak için System.Threading isim uzayını kullanmalıyız.

Örnek bir uygulama yapmak istiyorum. Bir adet listBox ve 4 adet buton kontrolünü forma yerleştirelim. (Başlat düğmesi, Duraklat Düğmesi, Devam Et Düğmesi, İptal Et Düğmesi)

Başlat düğmesine basınca sayılar yazılsın, bunu duraklatıp devam edebilelim ya da iptal edebilelim.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadDeneme0
{
   public partial class Thread1 : Form
   {
     public Thread1()
     {
       InitializeComponent();
     }

     private void Thread1_Load(object sender, EventArgs e)
     {
       Control.CheckForIllegalCrossThreadCalls = false;
     }
     Thread t = null;
     private void btnBaslat_Click(object sender, EventArgs e)
     {
       ThreadStart ts = new ThreadStart(Yazdir);
       t = new Thread(ts);
       t.Start();
     }
     public void Yazdir()
     {
       for (int i = 0; i < 10000; i++)
       {
         listBox1.Items.Add(i);
       }
       MessageBox.Show("Bitti");
     }

     private void btnDuraklat_Click(object sender, EventArgs e)
     {
       t.Suspend();
     }

     private void btnDevamEt_Click(object sender, EventArgs e)
     {
       t.Resume();
     }

     private void btnIptal_Click(object sender, EventArgs e)
     {
       t.Abort();
     }
   }
}

Şimdi yukarıdaki kodları açıklayalım. Başlat tuşuna basınca önce ThreadStart örneği yarattık. ThreadStart bir delegedir, yani metot işaretçisidir. Burada da Yazdır metoduna işaret etmektedir.Bu thread Yazdır() metotu içindeki kodu çalıştıracak. Peki neden bu delegeyi kullandık? Çünkü Thread class’ı parametre olarak bu delegeyi kullanmaktadır. Daha sonra Start() metotu ile thread başlatıldı.

Diğer düğmelere basınca neler olduğu gayet açık. Duraklatmak için Suspend(), devam etmek için Resume(), iptal etmek için Abort() metotları kullanılmış.

Asıl önemli nokta Control.CheckForIllegalCrossThreadCalls=false; satırıdır. Bu satırı çıkardığınızda

Şeklinde bir exception alırsınız. Diyor ki listbox1’e, yaratan thread dışında bir thread ile erişilmeye çalışılıyor. (Bu yöntem yerine Invoke() metodu da kullanılabilirdi delegeler ile.)

Uygulamayı çalıştırıp, bizzat görerek daha da iyi anlayacaksınız.

Eğer aranızda kurcalamayı sevenler varsa hemen fark etmişlerdir, başlat düğmesine birden çok kez basınca sıralama bir tuhaf oluyor. Diyelim 2 kez bastınız. 2 farklı thread’i başlattığınız anlamına gelir bu. 2 farklı thread 10000’e kadar sayar. Biri 2000’lerde iken diğeri 7000’lerde olur, ve karamaşık yazılırlar. Bunları belli bir şekilde düzenlemek, senkronize bir şekilde çalışmak gerekir.

Bunu sağlamak için btnBaslat_Click içine en son satıra t.Join(); yazarak thread’lerin sırayla çalışmasını sağlabiliriz. İstersek de belli bir süre de verebiliriz milisaniye cinsinden( t.Join(20); ).

Not: Thread ile çalışmanın hızını anlamak için Formun Load olayına 100000000’e kadar sayan bir döngü koyun. Aynı şeyi bir de thread ile yapın. Aradaki farkı çok net göreceksiniz. Birinde bitmeden form bile açılmayacak, diğerinde ise form hemen açılacak ve dolma işlemi bizim gözümüzün önünde devam edecektir.

Yukarıdaki örnek uygulamamızda dikkat ettiysek belli bir sayı yazdık döngüye. Bir de parametre ile sayıyı verelim isterseniz. Bunun için ParameterizedThreadStartkullanacağız.

Kodun değişen kısımlarını yazmak gerekirse:

Thread t=null;
   private void button1_Click(object sender, EventArgs e)
{
     ParameterizedThreadStart ts = new ParameterizedThreadStart(Yazdir);
     t = new Thread(ts);
     t.Start(1903);
     t.Join();
   }

   private void Yazdir(object deger)
   {
     for (int i = 0; i      {
       listBox1.Items.Add(i);
     }
     MessageBox.Show("Bitti");
   }

Böylelikle t.Start(1903) diyerek değeri parametre olarak da verebiliriz. Burada dikkat edilmesi gereken en önemli nokta Yazdir metodunun parametresinin object türünde olmasıdır.

Thread konusunun daha da anlaşılır olması için uygulamamıza bir eklenti daha yapacağım. 5. Bir düğme daha ekleyin forma. Click eventine ve altına aşağıdaki kodu yazın.

private void button5_Click(object sender, EventArgs e)
   {
     Thread u = new Thread(Goster);
     u.Start ;
   }
   void Goster()
   {
     MessageBox.Show("Siyah beyaz-ölüm yaşam");
   }

Normalde bir mesaj kutusu çıkınca sizinle etkileşime girerdi. Yani bir tuşa basardınız ve arka plandaki çalışma ondan sonra devam ederdi, siz mesaj kutusunda bir düğmeye vs. basmadıkça tüm işler dururdu. Ancak göreceksiniz ki bir tarafta mesaj kutusu açık kalacak, diğer tarafta ana forma geçiş yapabileceksiniz ve orada da işlerin sürdüğünü göreceksiniz.

Thread çok detaylı bir konudur. Burada daha gene kullanımını ve mantığını anlattım. İlgilenenler Race Condition, Deadlock, Thread Pool ve Senkronizasyon konularına da bakabilirler. Umarım bunları da yeni makalelerimde paylaşırım. Kafanızda thread hakkında bir şeyler oluştuğunu ümit ediyorum. Bir başka makalede görüşmek dileğiyle..

Gürkan Alkan - İstanbul Üniversitesi Bilgisayar Mühendisliği
grkn.alkan@gmail.com