Makale Özeti

Thread kullanmamızın amacı programımızda multitasking yaptırmak istememiz yani bir programa eş zamanlı olarak farklı işler yaptırmak istiyorsak bunu Thread’ler ile yaptırabiliriz.

Makale

Merhabalar , uzun zamandır Thread'ler ile ilgili bir uygulama yazarak biraz soyut olan bu konuyu iyice kavramayı istiyordum.Karşılaştığım sorunlar ile ilgili arama yaptığımda da çok fazla Türkçe kaynak olmadığını gördüm.Bu sebeple ben de yaşadıklarımı paylaşmak istedim. Thread kullanmamızın amacı programımızda multitasking yaptırmak istememiz yani bir programa eş zamanlı olarak farklı işler yaptırmak istiyorsak bunu Thread'ler ile yaptırabiliriz. Eğer tek işlemcili bir sistem kullanıyorsak bu tek işlemci threadleri çok hızlı çalıştırarak bize aslında iki olay eş zamanlı oluyormuş izlenimi verir.Fakat gerçekte olan şey belli bir süre botunca bir thread' i çalıştırmak , sonra onu bekletip diğer thread'i çalıştırmaktır. Şimdi Thread kullanım amacımı şöyle bir örnek ile açıklarsam sanıyorum ki daha anlaşılır olacaktır: Veritabanından çok fazla veriyi bir web servis ile çekmemiz gerektiğini ve bu işlemin dakikalar sürebileceğini düşünelim.Bu durumda kullanıcıya bir Progress Bar göstererek henüz verilerin yüklenmesinin devam ettiğini gösterirsek kullanıcı da verilerin ne kadarının yüklendiğini görecek ve bu bir anlam ifade edecektir.Aksi halde 5 dk boyunca programın kullanıcıya herhangi bir geri bildirim vermediğini görmek kullanıcıyı programın çalışıp çalışmadığı konusunda şüpheye düşürebilir.İşte bu durumda thread kullanmak gerekecektir. Şimdi ben veritabanından ListBox ' a kişi isimlerinin atıldığı bir uygulama yazarak bu süreci göstermek istiyorum.İlk önce veritabanımda Person adında bir tablo oluşturuyorum.Bu tabloyu aşağıda görebilirsiniz.



Daha sonra bu tabloya bir kaç veri giriyorum.



Veritabanım hazır.Şimdi uygulamamın ekran tasarımını aşağıdaki gibi yapıyorum.İstediğim şey Listbox'ıma tablomdaki isimler doldurulurken aşağıdaki ProgressBarın dolması ve işlemin devam ettiğini kullanıcıya gösterebilmek.Bittiğinde ise progressbar tamamen yeşil olarak gözüksün istiyorum.

Ve kişileri tutmak için bir de Person class'ı oluşturuyorum :

public class Person
    {
        public int ID;
        public string name;
    }
//Öncelikle gerekli değişkenleri tanımlıyorum.conSTR'yi kendinize göre değiştirmeniz gerekmekte.
public static string conSTR = @"Data Source=.\SQLEXPRESS;AttachDbFilename=c:\users\duygu\documents\visual studio 2010\Projects\WindowsFormsApplication5\WindowsFormsApplication5\MyDB.mdf;Integrated Security=True;User Instance=True";
        SqlConnection connection = new SqlConnection(conSTR);
        List<Person> persons = new List<Person>(); //kişilerden oluşan bir liste oluşturuyorum.
        SqlDataReader dr; //Verileri okumak için bir Datareader
        string sql = "select * from Person"; //SQL sorgum
        int count = 0; //bu değişkeni aşağıda açıklayacağım
        public Form1()
        {
            InitializeComponent();
            progressBar1.Maximum = 100;
            Thread t = new Thread(new ThreadStart(GetPersons)); //Thread'i oluşturduğum yer parametre olarak GetPersons methodunu veriyorum.
            t.Start();     // thread başladığı anda GetPersons methodu çağırılacak.         
        }

void GetPersons() // Veritabanından kişileri getiren methodumuz
        {
            SqlCommand cmd = new SqlCommand(sql, connection);
            connection.Open();
            dr = cmd.ExecuteReader();

            while (dr.Read())
            {
                Person p = new Person();
                p.ID = dr.GetInt32(0);
                p.name = dr.GetString(1);
                persons.Add(p); // veritabanından gelen bilgiler persons listesine alınıyor.
            }
            connection.Close();  

            for (int i = 0; i < persons.Count; i++)
            {
               listBox1.Items.Add(persons.ElementAt(i).name);  // Bu satırda aşağıda gördüğünüz cross thread hatası oluşacaktır.
                Thread.Sleep(100);
            }
        }
 

Yukarıdaki hali ile kodu çalıştırdığımızda aşağıdaki hatayı alırız.Aşağıdaki hata bize ListBox1 isimli kontrole bir başka threadin de ulaştığını söylüyor.Multithreading uygulamalar geliştirilirken bu hata ile karşılaşılmaktadır.

Bu hatanın sebebi Form oluşturulurken çağırılan threadlerin de bu Form üzerindeki nesneleri kullanıyor olmasıdır.


Bu hatayı çözmenin yolu delege kullanmaktır.

//AddListBoxItemDelegate adında bir delege(temsilci) oluşturuyoruz.
private delegate void AddListBoxItemDelegate(object item); private void AddListBoxItem(object item) { if (this.listBox1.InvokeRequired) // InvokeRequired eğer bir arayüz kontrolü bir thread tarafından oluşturuluyorsa o kontrolü sadece oluşturan threadin kullanabilir olması için kontrol ediliyor. { // Aynı thread tarafından kullanılıyorsa yeni bir delegeye bu görev atanır this.listBox1.Invoke(new AddListBoxItemDelegate(this.AddListBoxItem), item); // UpdateProgressBar();  bu kısmı ileride progressbarı değiştirmek için açacağız } else { // Invoke gerekmiyorsa listbox'a item'ı ekleriz. this.listBox1.Items.Add(item); } } Kodu bu şekilde düzenlediğimizde artık listbox'a veritabanından gelen bilgilerin eklenebildiğini göreceksiniz.Artık programın neresinde istersek orada sadece AddListBoxItem() methodunu kullanarak Listbox'a veri ekleyebiliriz.  
Şimdi sıra bu bilgileri eklerken Progressbarımızı doldurmaya geldi.Yukarıdaki kodda UpdateProgressBar() methodunu görmüşsünüzdür.Eğer o kod yerine
progressBar1.Increment(1); 
yazarsak yine cross thread hatası aldığımızı göreceksiniz.Sebep yine aynı bu sefer progressbar'ı kullanan birden fazla thread olacaktır.Şimdi aynı yöntemi kullanarak bir delege ve method da progressbarımız için tanımlıyoruz.
private delegate void UpdateProgressBarDelegate(object item);

        void UpdateProgressBarItem(object item)
        {
            count++;  // bu sayacın amacı işlemin bittiğini anlamaktır.

            if (this.progressBar1 .InvokeRequired)
            {

                this.progressBar1.Invoke(new UpdateProgressBarDelegate(this.UpdateProgressBarItem ),item);

            }
            else
            {
                if (count == persons.Count) // eğer person listesinin count'u bu sayaca eşit ise progressbar'ın maximum değeri verilerek işlemin bittiği kullanıcıya gösterilir.
                {
                    Thread.Sleep(100);
                    progressBar1.Value = progressBar1.Maximum;
                }
                else
                    progressBar1.Increment(1);

            }

        }
Şu anda programımız istediğimiz hale geldi.Veritabanından veriler çekilene kadar progressbar kullanıcıya bilgi verir durumda.Aşağıdaki ekran görüntüsü programın çalışma zamanında çekilmiştir.


Projenin kaynak kodlarına buradan ulaşabilirsiniz.