multi threading etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
multi threading etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

10 Mayıs 2012 Perşembe

ADO.NET 'de MARS ve Backgroundworker

Merhaba arkadaşlar.
Önceki yazılarımda, Ado.Net namespace 'lerini kullanarak "connected" mimaride uygulama geliştiriyorsak ve aynı bağlantı üzerinde birden fazla "DataReader" nesnesinin senkronize çalışmasını istiyorsak "MARS (MultipleActiveResultSets)" kullanmamız gerekliliğinden bahsetmiştim. "SqlConnection" nesnemizin "connection string" parametresine "MultipleActiveResultSets=true" ifadesini ekleyerek bu sorunu ortadan kaldırıyorduk. Bu işlemlerin daha bir senkronize ve hızlı gerçekleşebilmesi için genellikle "BackgroundWorker" kontrolü ile birlikte kullanılır. Bu yazımda MARS özelliğini ve "BackgroundWorker" kontrolünün birlikte nasıl kullanmamız gerektiğini inceleyeceğiz. Aşağıdaki gibi bir form hazırlıyorum.

Formumda bir "Button", iki "ListView", bir "ProgressBar" ve "BackgroundWorker" kontrollerini kullanacağım. "BackgroundWorker" arka planda "Thread" çalıştıracağından dolayı yasal olmayan thread çağrılarını kontrol edilmemesini "CheckForIllegalCrossThreadCalls = false" komutu ile sağlıyorum.

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

SqlConnection conneection = new SqlConnection("Server=. ; Database=Northwind ; trusted_connection=true");

private void btnProCat_Click(object sender, EventArgs e)
{
    listView1.Items.Clear();
    listView2.Items.Clear();
    backgroundWorker1.RunWorkerAsync();
    SqlCommand command = new SqlCommand("SELECT ProductID, ProductName, s.CompanyName, c.CategoryName, UnitsInStock, UnitPrice FROM dbo.Products p INNER JOIN dbo.Suppliers s ON p.SupplierID = s.SupplierID INNER JOIN dbo.Categories c ON c.CategoryID = p.CategoryID",   conneection);
    if (conneection.State != ConnectionState.Open)
    {
        conneection.Open();
        progressBar1.Style = ProgressBarStyle.Marquee;
    }
    SqlDataReader reader = command.ExecuteReader();
    while (reader.Read())
    {
        ListViewItem lvi = new ListViewItem();
        lvi.Text = reader.GetInt32(0).ToString();
        lvi.SubItems.Add(reader.GetString(1));
        lvi.SubItems.Add(reader.GetString(2));
        lvi.SubItems.Add(reader.GetString(3));
        lvi.SubItems.Add(reader.GetInt16(4).ToString());
        lvi.SubItems.Add(reader.GetDecimal(5).ToString());
        listView1.Items.Add(lvi);
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    SqlCommand com2 = new SqlCommand("SELECT CategoryID, CategoryName, Description FROM dbo.Categories", conneection);
    if (conneection.State == ConnectionState.Connecting)
    {
        Thread.Sleep(100);
    }
    if (conneection.State == ConnectionState.Closed)
    {
        conneection.Open();
        progressBar1.Style = ProgressBarStyle.Marquee;
    }
    SqlDataReader dr2 = com2.ExecuteReader();
    while (dr2.Read())
    {
        ListViewItem lvi = new ListViewItem();
        lvi.Text = dr2.GetInt32(0).ToString();
        lvi.SubItems.Add(dr2.GetString(1));
        lvi.SubItems.Add(dr2["Description"].ToString());
        listView2.Items.Add(lvi);
    }
    conneection.Close();
    progressBar1.Style = ProgressBarStyle.Blocks;
}

22 Nisan 2012 Pazar

C# 'da BackgroundWorker Detay

Merhaba arkadaşlar...
Bu yazım "kontrolü" makalemin devamı niteliğinde olacaktır, aynı örnek üzerinden eklemeler oolacaktır. Fakat daha detaylı inceleyeceğiz. BackgroundWorker 'ın "DoWork" event 'ine parametre göndereceğiz. Bunun için BackgroundWorker 'ı çalıştırdığım kod satırında BackgroundWorker 'ın "RunWorkerAsync" metodunun diğer "overload" 'ını kullanacağız. Benden object tipinde bir tane parametre ister, istediğim nesneyi gönderebilirim. Bunu görmek için formuma bir adet TextBox atıyorum. "RunWorkerAsync" metodumu çalıştırdığım yerde TextBox 'tan aldığım değeri "RunWorkerAsync" metoduma parametre olarak veriyorum.

        public Form1()
        {
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
        }
        private void btnIsYap_Click(object sender, EventArgs e)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(textBox1.Text))
                {
                    int i = int.Parse(textBox1.Text);
                    backgroundWorker1.RunWorkerAsync(i);
                    //i object 'e çevrildi (boxing)
                }
            }
            catch (Exception)
            {
                MessageBox.Show("Sayısal değer giriniz..");
            }
        }
"DoWork" event 'ine gelen arguman "e.Argument" ile alınır. Bu değer bizim ProgressBar kontrolünün maximum değeri olmalıdır. For döngüm bu değer kadar dönmeli ve bu sayıya kadar olan sayıları toplamasını isteyelim.

BackgroundWorker 'ın, "WorkerSupportsCancellation" özelliğini "true" yapmalıyım ki, uygulama çalışmasını sürdürürken, kullanıcı istediği zaman uygulamadan çıkabilsin. , Kullanıcının cancel yapıp yapmadığını da "DoWork" metodunda kontrol etmeliyim. Tek çekirdekli işlemciler "multi progress" çalışamaz. Yani aynı anda tek bir işlem yapabilirler. Birden fazla işlem yapması istenirse bu işlemleri sıraya koyar ve birinden bir parça diğerinden bir parça yapar. Bu işlemi de çok hızlı yaptığından dolayı işlemci, bize aynı anda birden fazla işlem yapıyormuş gibi görünür. Kullanıcının cancel işlemi yapıp yapmadığını, BackgroundWorker 'ın bool dönen, CancellationPending adındaki property 'si sayesinde kontrol edebilirim. Eğer BackgroundWorker 'ın kullandığı bu thread için bekleyen bir cancel işlemi varsa metot bize true dönecektir. Biz de "e.Cancel = true" yapacağız ve döngüden çıkacağız. Daha sonra kullanıcının uygulamayı cancel edebilmesi için btnDurdur_Click event 'inde backgroundWorker1.CancelAsync() metotunu çağırmam gerekiyor.

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            long toplam = 0;
            int j = int.Parse(e.Argument.ToString()); //i int'e çevrildi(unboxing) 
            for (int i = 0; i <= j; i++)
            {
                if (backgroundWorker1.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }
                toplam += i;
                this.Text = i.ToString();
                backgroundWorker1.ReportProgress(i / (j/100));
                System.Threading.Thread.Sleep(100);
            }
            e.Result = toplam.ToString();
        }
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                MessageBox.Show("İptal edildi..");
                progressBar1.Value = 0;
            }
            else
                MessageBox.Show(e.Result.ToString());
        }

        private void btnDurdur_Click(object sender, EventArgs e)
        {
            backgroundWorker1.CancelAsync();
            textBox1.Text = string.Empty;
            this.Text = 0.ToString();
        }

Daha sonra backgroundWorker1_RunWorkerCompleted event 'inde, eğer kullanıcı cancel ile çıkmışsa (e.Cancelled==true) ekrana bir mesaj vermesini istedim. Yok eğer kullanıcı çıkmadı ve işlem tamamlandıysa sonucu (e.Result) ekrana vermesini istedim. "e.Result", DoWorkEventArgs argumanın bir özelliğidir. e.Result 'i ben backgroundWorker1_DoWork event 'inden object olarak gönderdiğimden dolayı, backgroundWorker1_RunWorkerCompleted event 'inde, ekrana yazdırmak için string tipine çevirmeliyim.


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

19 Nisan 2012 Perşembe

C#' da BackgroundWorker Kullanımı

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();
        }
O da ne? Bir hata mesajı ile karşılaştık.
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..