Makale Özeti

C# ile asenkron programlama

Makale

Merhabalar,

Async ve await ile ilgili internette bir dünya yazı görmüssünüzdür. Uzun uzadıya anlatılır ve async’in detayına girilir genelde. Olması gereken de o aslında ama ben hızlıca async ve await’in ne olduğundan bahsedip Windows 8 ile normal bir uygulama geliştirirken ne kadar async kullandığınız konusu üzerinden bikaç örnekle anlatmaya çalışacağım. (Ayrıntıyı anlayıp sonuca erişmek için son 3 paragrafa gidebilirsiniz)

Async adından da anlaşılacağı gibi normal akan kod döngüsünde belirli bir kısmın o düzenden ayrılıp kendi başına çalışmasını sağlayan sistemdir. Bu sisteme geri bildirilir vs. konuları da var tabi. Bunu eskiden thread’ler yardımı ile yeni thread açarak hallediyorduk. Fakan orda da sürekli thread’in ana thread’den ayrı çalışması sonucunda UI erişim sıkıntısı ile karşı karşıya kalıyorduk. Burada diğer sorun da thread ile yeni thread açmanın işletim sistemine maliyetli olmasından kaynaklanıyordu. Bu yüzden eğer büyük işlemler yapmıyorsanız thread işleminden uzak durun deniliyordu. Peki async ile ne oldu. Async aslında temel olarak aynı şekilde thread yönetimi üzerinden gidiyor. E temeli thread ise neden async var? Temelde thread mimarisi var ancak onun üzerinde işletim sistemine Task’lar tanımlıyor async metotlar. Bundan sonra gerekli thread açım kapatım işlmelerini tasklar üzerinden işletim sistemi hallediyor. O zaman hikayemiz artık async ile task yönetimine dönüştü. Şimdi işin hikaye kısmını atlayıp gerçeklerle girelim.

Alttaki daha önceki yazılarımızdan birinde kullandığımız kodlardan birini biraz açalım.

public async Task FileExists(string fileName)
        {
            try
            {
                var file = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

Yukarıdaki kod Windows 8 içerisinde uygulama dosyamız içerisinde verilen isimli dosyanın olup olmadığına bakıyor. önce ..LocalFolder.GetFileAsync(fileName); bu kısma takılmadan metodun tanımlanmasına bakalım. async Task … kullanmış. Buranın amacı ben async bir metodum ve içerimden bir tane task dönecek ve bu arkadaş da sonucunda bir tane bool değer içeriyor anlamına geliyor. Bu çağrıldığı yere (örneğin bir button click) nasıl gidiyor, işleniyor ona da birazdan dönecem. Biraz daha ilerlersem try içerisinde bir tane “file” tanımlanmış (türü StorageFile, bizi ilgilendirmiyor şu an) ve await denilip ..LocalFolder.GetFileAsync(fileName); denilmiş. Burada ne olduğu benim async ile ilgili anlatacağım şeylerin %70′i. Burada diyorum ki sen git dosya almaya çalış, ancak ben de await ile senin dosyanı almanı bekleyeyim. (await üzerinde durmadık ama await verilen taskın bitmesini bekletir sonra alt satırları işletir.) Peki dosya almaya giderken nasıl gidecek, eski usülde olduğu gibi onlar işlenirken bekleyecek mi? Hayır, ..LocalFolder.GetFileAsync(fileName); aslında bize bi adet task döndürüyor.(IasyncOperation interface’i üzerinden gelir ama karıştırmamak için task diyelim şu an) Ben de diyorum ki burda bi task varmış, bu taskın bitmesini bekleyeyim. İşte bu işlemi await yapıyor. Tamam burası çat pat oldu gibi async’i görünce alt satıra geçmesin diye await yazdık bitti. Peki burdan dönen Task değerinin çağrıldığı yer ne durumda? Şimdi ona bakalım.

private void BtnTikla_click(object sender, RoutedEventArgs e)
        {
           bool sonuc= fileExists("osman.png").Result;  
        }

Bu şekilde tanımlarsınız ve kullanabilirsini bile. Ancak burada siz butona basınca arka plan taskı diğer metot içerisinde deneneceği için UI tarafında kilitlenme meydana gelir. Zaten biz bunu istemiyoruz o zaman void’den önce bi tane async ekleyelim.

private async void BtnTikla_click(object sender, RoutedEventArgs e)
        {
           bool sonuc= fileExists("osman.png").Result;  
        }

Süper Böyle yaptık ancak bu sefer metodumuzun altı çizildi ve bize dedi ki siz bunu async tanımladınız ama await kullanmadığınız için bu senkron çalışacak dedi.

O zaman yine bizim için bi faydası kalmadı, senkron olacak ise az önceki de senkrondu. Ne gerek vardı ki async eklemeye. Bide sonuna result deyip sonucunu da alıyoruz ama yine bekletecek bizi. O zaman istediği await’i de ekleyelim. Hatta önce oradaki result’ı çıkarıp gelen değerin ne olduğuna bakalım bi, onu atladık sanırım.

private async void BtnTikla_click(object sender, RoutedEventArgs e)
        {
           Task sonuc= fileExists("osman.png");  
        }

Oradaki result’ı çıkarınca bize direk bir task döndürdü, hatta tam dönen değer Task tipinde. Biz bu taskın Result’ını alıp kullanmıştık az önce. Şimdi kaldığımız yere geri dönüp await’imizi ekleyelim.

private async void BtnTikla_click(object sender, RoutedEventArgs e)
        {
           bool sonuc=await fileExists("osman.png");  
        }

Şu an ne result ekledik ne task ekledik sadece await diyerek gelen cevabın bool bir değer olduğunu verdi bize. Peki şu iki kodu karşılaştıracak olursak ne olacak?

private async void BtnTikla_click(object sender, RoutedEventArgs e)
        {
            for (int i= 0; i < 100; i++)
            {
                bool sonuc=fileExists("osman.png",i).Result;  
                Debug.WriteLine("Btn i:"+i.ToString());
            }
           
        }
        public async Task fileExists(string fileName,int i)
        {
            try
            {
                var file = Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(fileName).AsTask().Result;
                return true;
            }
            catch (Exception)
            {
                Debug.WriteLine("Get i:" + i.ToString());
                return false;
            }
        }

Interface’i de task türüne çevirip result değerini aldım ve üsttekinin de result değerini aldım. Şimdi ikisinin de await olduğu kodu koyayım:

private async void BtnTikla_click(object sender, RoutedEventArgs e)
        {
            for (int i= 0; i < 100; i++)
            {
                bool sonuc=await fileExists("osman.png",i);  
                Debug.WriteLine("Btn i:"+i.ToString());
            }         
           
        }
        public async Task fileExists(string fileName,int i)
        {
            try
            {
                var file =await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
                return true;
            }
            catch (Exception)
            {
                Debug.WriteLine("Get i:" + i.ToString());
                return false;
            }
        }

Bunun ekran çıktısı:

İkisi arasındaki fark tam olarak şöyle: İlk koyduğum senkron çalışan içerisinde butona tıklayınca 4 saniye dondu sonra tepki verdi, ikinci await kullandığımızda ise sanki altında bir döngü ve işlem yokmuş gibi tepki verdi. O for döngüsü orda değil de başka bir yerde olsaydı mesela:

private void BtnTikla_click(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 100; i++)
            {
                Helper(i);
                Debug.WriteLine("Btn i:" + i.ToString());
            }
        }
        private async void Helper(int i)
        {
            bool sonuc = await fileExists("osman.png", i);
        }
        public async Task fileExists(string fileName, int i)
        {
            try
            {
                var file = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
                return true;
            }
            catch (Exception)
            {
                Debug.WriteLine("Get i:" + i.ToString());
                return false;
            }
        }

Bu şekilde çalıştırmış olsaydım btnClick altındaki 100′e kadar olan kısmı sayana kadar UI donar fakat sonra devam eder. Ekran çıktısı nasıl olur peki:

İlk versiyon senkron, ikinci versiyon asenkron şekilde aynı ekran çıktılarını verdiler ve 1.nin tepki süresi 4 sn, ikincinin 0 sn sürmüştü. Son yazdığımız, farklı yerden çağıranın, ekran çıktısından da anlaşılacağı gibi önce içteki for yapılıyor, arka planda da dosya kontrolü yapıyor. Tepki süresi 1.5 sn civarında. Get değerlerinin düzenli gelmiş olmasına bakmayın hepsinde exception aldığı için eşit tepki süresi verdi. Farklı işlemler olsaydı o get’in yanındaki sayı da dağınık olcaltı.

Kısacası async işleminizi ana thread’den ayırıp baska thread de yaparak akıcı arayüz ve paralel işlem yeteneği sağlıyor. await ile async’in gerçekten async olması sağlanıyor. Metotlarınızı async yapmaya korkmayın dönen değeri de await yapacağınız için task geldi pars etcem gibi şeylerle uğraşmayacaksınız. Bikaç önemli noktayı tekrar belirtmek istiyorum.

  1. await sadece async metotlar içerisinde kullanılır.
  2. async Taskdeneme dediğinizde gelen değeri string sonuc=await deneme(); derseniz direk stringi almış olursunuz.
  3. Async metot kullanıyorken kesinlikle await kullanın.(istisna birazdan belirtilecek)
  4. Eğer string gibi geriye değer döndürmeyecekseniz Task türünden async Task Deneme(){} şeklinde değer döndürün.
  5. 4. madde yerine async void Deneme(){} şeklinde kullanım yaparsanız hem ‘deneme’ işleminin bittiğinden emin olamazsınız, hem de ‘deneme’ içerisinde oluşan hataları deneme metodunu çağıran yerde yakalayamazsınız. async void işlemi başlat ve unut olarak bilinir ve açıkcası kullanılması nerdeyse hiç önerilmez.
  6. Async yapmak istediğiniz herhangi bir metodu başına async ekleyerek yapabilirsiniz. (constructer hariç)

Not:WinRT üzerinde aklınıza gelebilecek çoğu şey async tanımlanmış durumda. Ne kadar açık oldu bilmem ama en azından sizi task yönetimine karıştırmadan winRT uygulaması yazmanıza yetebilecek bilgi vermiş olmayı umuyorum. Asenkron ile ilgili sanırım Fatih Boy hoca ve İlkay İlknur derinlemesine yazılar yazmıştı. Onlara da göz atabilirsiniz.

Teşekkür Ederim.