İlkay İlknur

just a developer...

Task.FromResult Metodu Ne İş Yapar, Nerede Kullanılır ?

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bugün C# ile kod yazarken en sık kullandığımız keywordlerden ikisi şüphesiz ki async & await keywordleri. Her ne kadar bu keywordleri bilinçli olarak kullanıyoruz muyuz konusunda tam emin olamasam da bazen async & await keywordunu kullanmaktan biraz daha ötesine geçmek gerekiyor.

Task objesi bildiğimiz üzere .NET içerisinde bir asenkron operasyonla ilgili tüm bilgileri içerisinde tutan sınıf. Yani bir asenkron operasyonun o anki durumu ne ? veya işlem gerçekleşirken bir exception oldu mu ? gibi sorularımız cevaplarını alacağımız sınıf bu sınıf. Dolayısıyla C# içerisindeki async ve await keywordleri de bu task tipini kullanarak arka planda uygun bir şekilde gereken yapıları oluşturuyorlar.

Task içerisindeki FromResult metodu da en basit anlamda yeni bir Task objesi yaratarak Task'ın durumunu tamamlanmış olarak işaretliyor ve parametre olarak verdiğiniz değeri de Task'ın sonucu olarak içerisine atıyor. Bu durumda aklınıza şu soru geliyor olabilir. Normalde async & await kullandığımızda Task yaratmamız gerekmiyor. Peki bu metot neden var ? Neden ortada bir asenkron operasyon yokken Task yaratmak durumunda kalıyoruz ? Hemen kısaca kullanım alanlarına bakalım...

Interface içerisindeki asenkron metot implementasyonları

Diyelim ki 3rd party bir kütüphane kullanıyorsunuz ve o kütüphaneyi kullanabilmeniz içinde belirli bir interface'i implemente eden bir tip yaratmanız gerekiyor.

interface IThirdLib
{
    Task<int> CountAsync();
}

Bu interface içerisindeki CountAsync metodunu implemente etmek istediğinizde bulunduğunuz platformda bu işlem asenkron yapılmıyor olabilir veya static bir değer döndürmeniz gerekebilir.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return 0;
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Directory.EnumerateDirectories(@"C:\").Count();
    }
}

Bu durumda async await keywordu kullanmadığımız için Task tipini arka planda compiler bizim için yaratmıyor ve dolayısıyla int'i ben Task tipine çeviremem diye hata veriyor. Aklınıza hemen Task.Run yaparım onu da await ederim geliyorsa hemen o fikri unutun ve sakın yapmayın. Task.Run'ı şuursuz olarak her Task gereken yerde kullanmamak gerektiğini sakın unutmayın.

Yukarıdaki gibi bir durumla karşılırsanız Task.FromResult metodunu kullanarak tamamlanmış bir Task objesi yaratıp onu metotdan dönebilirsiniz.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(5);
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(Directory.EnumerateDirectories(@"C:\").Count());
    }
}

Böylece bizim implementasyonumuz senkron çalışacak ama aynı zamanda bizim yazdığımız tipi kullananlar da async await kullanarak metodumuzu çağırsalar bile herhangi bir sorun yaşamayacaklar.

Bazı durumlarda senkron bazı durumlarda asenkron çalışan metotlar

Yazdığımız metotlar duruma göre senkron veya asenkron çalışabilirler. Örneğin bazı durumlarda senkron olarak validasyon yapılıp bunun sonucuna göre asenkron operasyona başlamadan exception fırlatılabilir. Örneğin MSDN'deki şu örneğe bakarsak...

static Task<long> GetFileLengthsAsync(string filePath)
{
    if (!Directory.Exists(filePath))
    {
        return Task.FromException<long>(
                    new DirectoryNotFoundException("Invalid directory name."));
    }
    else
    {
        string[] files = Directory.GetFiles(filePath);
        if (files.Length == 0)
            return Task.FromResult(0L);
        else
            return Task.Run(() => {
                long total = 0;
                Parallel.ForEach(files, (fileName) => {
                    var fs = new FileStream(fileName, FileMode.Open,
                                            FileAccess.Read, FileShare.ReadWrite,
                                            256, true);
                    long length = fs.Length;
                    Interlocked.Add(ref total, length);
                    fs.Close();
                });
                return total;
            });
    }
}

Bir klasörüdeki tüm dosyaların toplam büyüklüğünü almak istediğimizde örneğin ilk if'e bakarsanız klasörün validasyonu yapılıyor ve eğer klasör yoksa metot doğrudan geri dönüyor. Else içerisindeki 1. if'e bakarsak da klasör içerisinde hiç dosya yoksa doğrudan 0 dönüyor. Sonrasında ise Task.Run ile asenkron operasyon başlatılıyor. Dolayısıyla bu metot aslında en içteki else ifadesine kadar senkron çalışıyor. Sonrasında ise asenkron olarak devam ediyor. İşte bu gibi durumlarda da Task.FromResult metodu kullanılabilir.

Task.FromResult'a benzer diğer metotlar

Task tipi içerisinde FromResult metoduna benzeyen başka metotlar da bulabilmeniz mümkün. Örneğin, FromException veya FromCancelled gibi metotlar. Bu metotlar da isimlerindeki durumlara uyan Task tiplerini yaratıp size verirler. İçerisindeki exception olan task tipi veya iptal edilen bir Task tipi gibi...

Task.CompletedTask

.NET Framework 4.6 ile beraber Task içerisine CompletedTask propertysi eklendi. Bu Task tipi de herhangi bir değer döndürmeyen tamamlanış bir Task objesi içerisinde barındırıyor. Yani yukarıdaki gibi durumlardan birinde metodun dönüş tipi Task<int> değil de Task olsaydı Task.CompletedTask kullanabiliriz.

Taskları Cacheleme

Task.FromResult metodu her çağırdığımızda yeni bir Task objesi bize geri veriyor. Dolayısıyla her Task.FromResult(0) dediğimizde hep sonuç olarak 0 değerini barındıran bir task bize geri dönüyor.

Bu gibi durumlarda sık sık bu metodun çağırılması aslında gereksiz bir şekilde object yaratılmasına ve memoryde gereksiz bir şekilde fazladan bu objelerin yer almasına neden oluyor. Bu gibi durumlarda sıkça başvurulan yöntemlerden biri bu Taskların cachelenmesi. Bunun için örnek olarak Roslyn içerisindeki Task cacheleme mekanizmasına bakmanızı tavsiye ederim.

Roslyn içerisindeki SpecializedTasks sınıfı source code'u : https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/Utilities/SpecializedTasks.cs



C# 5.0 Asenkron Programlama Özelliklerini .NET Framework 4.0, Windows Phone 7.5 & 8.0 ve Silverlight 4.0 & 5.0 Platformlarında Kullanın

Blogda bulunan C# 5.0 Asenkron programa ilgili makaleleri okuyanlardan zaman zaman "Acaba bu özellikleri .NET 4.0 veya Windows Phone 7.5 gibi platformlarda kullanabiliyor muyuz ? " gibi mailler alıyorum. Tabi .NET 4.0 kullananlar için projenizi .NET 4.5'e yükseltip kullanın demek verilebilecek en kolay cevap :) Ama tabi projeyi yükseltin demek her zaman söylendiği kadar kolay olamayabiliyor :) Çok kapsamlı çalışmaların, testlerin yapılması gerekebiliyor. Bu nedenle tabi ki insanlar projeyi yükseltmekten ziyade daha kolay yoldan bu özellikleri kullanabilmeyi tercih edebiliyorlar. Windows Phone veya Silverlight tarafına geçtiğimizde ise tabi ki C# 5.0 compilerının yanında compilerın arka planda kullandığı yapılarında bu platformlara taşınmış olması gerekiyor. Neyse çok fazla uzatmayalım ve şöyle Visual Studio 2012 ile .NET Framework 4.0 'da bir Console uygulaması yaratalım ve duruma bir bakalım :)

Şimdi basit bir async metot yazmaya çalışalım ve bakalım nasıl bir durumla karşı karşıya kalacağız.

Çok basit bir şekilde C# 5.0 ile yapabildiğimiz, bir işlemi arka planda çalıştırma olayını .NET Framework 4.0 ile gerçekleştirdik. Şimdi projemizi derleyelim ve bakalım bir sorun ile karşılaşacak mıyız ?

Mesaj aslında oldukça net :) Async ve await keywordlerini C# 5.0 compilerını kullandığımız için kullanabilmekteyiz. Ancak compilerın arka planda kullanacağı tipler haliyle .NET Framework 4.0 içerisinde bulunmuyor ve compiler da haliyle kodu arka planda yeniden yazmak için bu tiplere ihtiyaç duyuyor . Peki compilerın ihtiyacı olan bu tipleri acaba dışarıdan bir library aracılığıyla versek :) kısayoldan sonuca ulaşabilir miyiz ?

Microsoft.Bcl.Async

Microsoft.Bcl.Async nuget paketi Microsoft tarafından geliştirilen ve .NET Framework 4.0, Windows Phone 7.5 / 8.0 ve Silverlight 4.0/5.0 platformları için compilerın arka planda ihtiyaç duyduğu tipleri içerisinde barındıran bir paket. Bunun yanında ilgili platformlarda bulunan bazı tiplere task-based asenkron metotlar da extension metot olarak bu paket sayesinde gelmekte. O zaman bu package'ı projemize ekleyelim ve bakalım nasıl bir değişiklik olacak.

Microsoft.Bcl.Async paketini proye ekledikten sonra projemiz sorunsuz bir şekilde derlendi. Bunun yanında eğer WebClient tipi gibi içerisinde asenkron metotlar bulunan tiplere bakarsak bu tipler içerisinde de task-based asenkron metotların extension metotlar olarak eklendiğini görüyor olacağız.

Gördüğünüz gibi artık C'# 5.0 özelliklerini .NET 4.0, Silverlight ve Windows Phone'da kullanmak oldukça kolay :)  Yapmanız gereken tek şey proje solutionınızı Visual Studio 2012'ye yükseltemek. Evet yanlış okumadınız :) Şu ana kadar yaptıklarımız sadece Visual Studio 2012 içerisinde geçerli. En azından sizi framework upgrade'inden kurtardım ;) Umarım sizler için faydalı bir yazı olmuştur.


Bir C# Developerının Windows Phone 8 SDK'inden Beklentileri ve Hayal Kırıklıkları

Geçenlerde Windows Phone 8 SDK'i ile beraber gelen yenilikleri didiklerken bir yandan da SDK'in release notlarina gözatiyordum. Listedeki maddelere bakarken gözüm bir anda programlama dili tarafindaki yenilklere takildi  ve notlar içerisinde C# 4.0 ve C# 5.0 ile beraber gelen yenilikleri kullanabilecegimizi gördüm. Ben de bu release notlarini okuduktan sonra kollari sivadim ve C# 4.0 ve C# 5.0 yeniliklerini nereye kadar kullanabildigimizi, SDK'in bu özelliklerin ne kadarini destekledigini incelemeye basladim. Bakalim SDK benim beklentilerimle ne kadar uyusmus :) Iste basliyoruz...

C# 4.0 dynamic Keywordü

C# 4.0'in temasi olan dinamik programlama çerçevesinde C# tarafinda yapilan en büyük yeniliklerden biri dynamic keywordünün dil içerisine eklenmesi. dynamic keywordü ile beraber bildigimiz gibi nesneler içerisindeki üye ve metot erisimlerinde compilerin derleme islemi sirasinda yaptigi tip kontrollerini de dogrudan derleme zamani yerine çalisma zamanina tasiyarak gerçeklestirebiliyoruz. Böylece çogu zaman reflection kullanarak 5-10 satirda yazacagimiz islemi dynamic keywordü sayesinde  1 satirda halledebiliyoruz.

Windows Phone tarafinda dynamic keywordü nerede etkin olarak isimize yarar diye düsündügümde aklima ilk sirada JSON islemleri geliyor. Dönen bir JSON'i deserialize ederek elimizdeki nesnedeki üyelere dinamik olarak erismek aklima ilk gelen senaryo.

Not : Bu islemi aslinda dönen JSON'in formatina uygun tipler hazirlayarak da gerçeklestirebiliriz. Böylece derleme zamaninda elimizde ilgili nesne formatlari olur ve buna uygun olarak kodumuzu yazariz. Ancak her dönen JSON formatina göre nesneler olusturmak bizim isimizi zorlastirir. Yoksa illa ki dynamic kullanacagiz diye bir sart yok :)

Ilk olarak bir console uygulamasi ile basit  bir JSON ürettim. Ürettigim JSON  içerisinde de  Customer tipinden bir array bulunmakta. Customer tipinin propertyleri ise asagidaki gibi.

[DataContract]
public class Customer
{
    [DataMember]
    public int Id { getset; }

    [DataMember]
    public string Name { getset; }
}

Sonrasinda ise yazdigim Console uygulamasindan asagidaki gibi içerisinde dummy elemanlar bulunduran bir JSON elde ettim.

[ { "Id":1, "Name":"Ilkay" }, { "Id":2, "Name":"Ahmet" }, { "Id":3, "Name":"Osman" } ]

Test için gerekli JSON'i elde ettikten sonra simdi sira geldi Windows Phone uygulamasina. Hemen yeni bir Windows Phone 8 uygulamasi yaratiyoruz ve projemize Nuget'ten asagidaki komutla JSON.NET kütüphanesini ekliyoruz.

Not : Nuget yerine JSON.NET projesinin codeplex sayfasina giderek ilgili dll'leri bilgisayariniza indiribilir ve indirdiginiz dll'i manuel olarak Windows Phone 8 projenize ekleyebilirsiniz.

JSON.NET Kütüphanesini Projeye Eklenmek Için Gereken Nuget komutu : PM> Install-Package Newtonsoft.JSON

Nuget paketini de projemize ekledikten sonra sira geldi kodlama asamasina.

public void DynamicJsonTest()
{
    string json = "[{\"Id\":1,\"Name\":\"Ilkay\"},{\"Id\":2,\"Name\":\"Ahmet\"},{\"Id\":3,\"Name\":\"Osman\"}]";

    dynamic list = JArray.Parse(json);

    foreach (dynamic customer in list)
    {
        var id = customer.Id;
        var name = customer.Name;
    }
}

Gördügünüz gibi test amaciyla oldukça basit bir kod yazdim. DynamicJsonTest isimli metot içerisinde ilk olarak json isimli degiskenin içerisine daha önceden elde ettigim JSON stringini attim. Sonrasinda ise JSON.NET içerisindeki JArray tipinin içerisinde bulunan static Parse metodunu kullanip buradan dönecek olan nesneyi de dynamic olarak tanimladim. Böylece bana gelecek olan nesne içerisindeki propertylere kolay bir bir sekilde dinamik olarak erisebilir olacagim. Sonrasinda ise elimde olan array içerisindeki elemanlari foreach ile dönerek yine dynamic olarak tanimladigim her bir elemanin içerisinde bulunan Id ve Name propertylerini okumaya çalistim.

Eger burada dynamic keywordünü kullanmasaydim zaten bu sekilde kod yazmam mümkün degildi. Ben dynamic kullanarak kodun derlenme asamasinda bu kisimlarda yapilacak olan type-check islemini compilerin çalisma zamanina birakmasini sagladim.

Simdi yazdigimiz metodu, uygulama içerisindeki page açildiginda çagirilacak sekilde düzenleyelim ve uygulamayi çalistiralim.

class MainPage
{
    public MainPage()
    {
        InitializeComponent();

        DynamicJsonTest();
    }
}

Olmadi :( Itiraf etmem gerekirse bu hayal kirikligi Microsoft ile ilgili degil. Yani bu noktada olmamis diyerek topu direkt MS'e atamiyorum. :) Bu exceptioni almamizin temel nedeni kullandigimiz JSON.NET kütüphanesinin Windows Phone SDK 7.1 için yazilmis olmasi. Özetle bu senaryoyu test edebilmemiz için JSON.NET'in tam anlamiyla Windows Phone 8 destekleyen sürümünü beklememiz gerekmekte. Ancak tabi ki Windows Phone tarafinda dynamic keywordünün eklenmesi, benim aklima ilk olarak JSON senaryosunu getirmisti. Ancak simdilik bu konuda basarili bir sonuç alamadik. Gelelim ikinci senaryomuza.

ExpandoObject

Windows Phone içerisinde dynamic keywordü denince aklima ikinci olarak nedense ExpandoObject geldi. :) ExpandoObject basit olarak dynamic keywordü ile kullanildiginda içerisine dinamik olarak property eklenebilen bir tip. Windows Phone tarafinda ne ise yarar, nerelerde kullanilir sorularina cevap bulamasam da acaba eklemisler mi diye SDK içerisine bakamadan edemedim. :) Bir de baktim ki ExpandoObject orada System.Dynamic namespace'i içerisinde duruyor :) Hemen hizli bir sekilde asagidaki gibi test kodumu yazdim ve testimi gerçeklestirdim.

public void ExpandoObjectTest()
{
    dynamic expando = new ExpandoObject();
    expando.Id = 1;
    expando.Name = "Ilkay";

    MessageBox.Show(String.Format("ExpandoObject Degerleri Id={0} Name={1}", expando.Id, expando.Name));
}

Yukaridaki kodu uygulama ilk açildiginda page içerisinde çalistirdigimizda bakalim ne olmus ?

Bu testimizde basarili olduk. Ancak dedigim gibi ExpandoObject Windows Phone tarafinda nerede isimize yarar ben bulamadim. Hatta normalde de nerede kullanabilirim sorusuna cevap bulmus degilim :)

Özetle C# 4.0 Özellikleri

Yukaridaki iki madde açikcasi dynamic programlama özelliginde aklima gelen spesifik senaryolar. Tabi ki dynamic kullanabilecegimiz pek çok senaryo Windows Phone içerisinde mevcut. Özellikle reflection kullanmamiz gereken yerlerde artik dynamic ile ilerleyecek olmamiz bile bizim için oldukça önemli. Simdi geçelim C# 5.0 kismina.

C# 5.0 Async

C# 5.0 diyince hepimizin aklina asenkron programlama geliyor. Bildigimiz üzere C# 5.0 ile beraber gelen async ve await keywordleri ile beraber asenkron çagrimlari çok kolay bir sekilde gerçeklestirebiliyoruz ve callback implementasyonlarindan bu sayede yirtabiliyoruz :) Windows Phone tarafinda da yeni SDK ile beraber async ve await keywordlerini kullanabiliyormusuz. Tabi burada aklima gelen bu keywordleri kullanmanin ötesinde SDK içerisinde bulunan tiplerin  task tabanli asenkron patternini ne kadar uyguladiklarini incelemek oldu. Bunun için aklima gelen ilk tip WebClient tipi ve dolayisiyla da DownloadStringAsync metodu oldu. Bakalim bekledigimiz implementasyonu görebilecek miyiz ?

Varan 1 :) Callbackler orada öylece durup duruyor :D Async & await kullanarak ben internetten bir content download edemeyeceksem ne anladim bu isten. :) Neyse moralimi bozmayayim dedim bakalim belki baska yerlerde bu özelligi kullanabiliyorumdur. Sonra aklima gelen ikinci yer ve bana göre de en kritik nokta olan webServis baglantilari. Özellikle Business appleri gelistirme senaryolarinda tüm business logiclerini webservis tarafina yiktigimizda yazdigimiz uygulamanin web servise olan bagimliligi oldukça fazla oluyor. Web servis çagrilarini asenkron olarak yaptigimizdan dolayi da tüm kod neredeyse callback çöplügüne dönüyor. Özellikle Windows 8 tarafinda web servis proxylerinin task-based olarak üretilmesiyle artik yaptigimiz asenkron web servis çagrilarinda async ve await'i hizli bir sekilde kullanabilmekteyiz. Bakalim Windows Phone 8 tarafinda bu durum nasilmis ?

Hemen Visual Studio üzerinden Add Service Reference'a tikladim ve oradan da sol alttaki Advanced butonuna tiklayarak karsima çikan ekrana öylesine uzun süre baktim :( Neden mi ?

Varan 2 :( Itiraf etmem gerekirse WebClient vs... neyse de web servisleri tarafinda durumun böyle olmasi bu konuda beni bayagi hayal kirikligina ugratti. Bunun üzerine son bir umut asenkron islemlerin yine sikça kullanildigi launchers and choosers kismini test etmek geldi aklima. Insanda bir umut iste :)

Sonuç ne mi oldu ?

Yine hayal kirikligi :(

Tüm bu yaptigim testlerden sonra anladgim kadariyla Microsoft mevcut Windows Phone API'larina hiç dokunmamis. Bunun yaninda yeni gelen API'larda ise asenkron islemler C# 5.0 ile beraber gelen async ve await keywordleri ile kullanilabilecek sekilde task-based olarak implemente edilmis.

Özetle C# 5.0 Özellikleri

Kagit üzerinde baktigimizda Windows Phone 8 SDK'i içerisinde C# 5.0 ile beraber gelen asenkron programlama yeniliklerini kullanabiliyoruz. Ancak bu yenilikleri kullanabilecegimiz senaryolara baktigimizda maalesef henüz ilgili destegin verilmedigini görüyoruz SDK içerisinde. Bu nedenle Windows Phone 8 SDK'i C# 5.0 yeniliklerini kullanabilme adina bence sinifta kaldi. Blog yazilarina islerim dolayisiyla bir süre ara verdikten sonra böyle bir inceleme yazisiyla dönmek istedim. Tabi biraz iç karartici oldu orasi ayri :) 2012'nin son blog postu olan bu yazim vesilesiyle hepinize iyi yillar dilerim !


Windows 8 Metro Style Data Uygulamaları ve GridView Kontrolü - C# & XAML

Merhaba Arkadaşlar,

Windows 8 metro style development konusuna giriş yaptığımız yazılarımızın ardından verdiğimiz kısa aradan sonra kaldığımız yerden serimize devam ediyoruz :). Ara vermemizin sebebi de aslında Windows 8 RP ve Visual Studio 2012 RC sürümlerinin çıkmasını beklememizdi. Kabul ediyorum biraz da benim tembelliğimdi :)

Bu yazımızda ise sizlerle Windows 8 Metro Style Data uygulamalarına giriş yapıyor olacağız ve bu kapsamda da GridView kontrolünü incelemeye başlayacağız. Ancak öncelikle Windows 8 tarafındaki data erişimine bir bakalım.

Wind0ws 8 Metro Style Uygulamalarında Veri Erişimi

Windows 8 tarafında bir data-driven Metro Style Uygulama geliştirmek istediğimizde aslında temelde data çekebileceğimiz 2 kaynak bulunmakta.

Bunlar,

  • Web üzerinde bulunan kaynaklar
  • Database içerisinde saklanan veriler.

Web üzerinden bulunan kaynaklardan kastımız aslında RSS gibi veya REST tabanlı doğrudan HTTP tabanlı isteklerde bulunup yanıtı alabileceğimiz kaynaklar.

Database'den kastımız da database içerisinde bulunan veriler ve bu verilerin Windows 8 Metro Style uygulamalarda kullanılması.

Web üzerinde bulunan kaynaklara erişimi ise WinRT üzerinde asenkron olarak gerçekleştirebilmekteyiz. Bunun nedeni de aslında web üzerindeki kaynağa erişim sırasında uygulamanın UI threadini bloklamamak ve kullanıcıya daha iyi bir kullanıcı deneyimi sunabilmek.

Şimdi database tarafına gelirsek. Elimizde bir WPF uygulamamız olduğunu düşünelim ve bu uygulamayı Windows 8 Metro Style uygulamaya çevirmeye çalışacağımız varsayalım. Aklımıza gelecek sorulardan ilki elimizdeki verileri Windows 8 Metro ortamına nasıl aktaracağımız olacak. Çünkü WPF tarafında code-behind tarafında SqlConnection sql = new SqlConnection(); şeklinde database'e bağlanabiliyorduk. (Bu kullanımın doğru olduğunu savunmuyorum sadece böyle yazıldığını varsayıyorum :) ) Peki Windows 8 Metro tarafında da işler böyle yürüyor mu ?

Cevap hayır :) Öyle direk hayır deme bize nedenlerini söyle diyecekseniz peki o zaman başlıyorum :)

  • Öncelikle yazdığınız uygulamanın ARM işlemcili tabletlerde dahi çalışacağını unutmayın. Bu nedenle database manipülasyonunu bu cihazlar üzerinden yapmak dönen datayı işlemek gibi işlemler sizin uygulamanızın performansını oldukça etkileyecektir. Ayrıca uygulamanız amacını da aşacaktır. Bunun yanında data cacheleme mekanizmaları da bu senaryoda olmayacaktır.
  • İkinci nedenimiz de geliştirdiğimiz uygulamada veri erişim kısmında bir değişiklik olacağı zaman bunu tabletteki uygulamayı güncelleyerek mi yapacağız. :) Sizin uygulamanız belki farklı platformlarda da kullanılıyor ve bir platformdaki istek için database de bazı güncellemeler yaptınız ve aslında bu yaptığınız değişiklik sizin uygulamanızda veriyi gösterme anlamında hiçbirşey ifade etmiyor ancak veri erişim katmanında değişiklik gerekiyor. Bu durumda Marketplace'e uygulamamızın update'ini mi göndereceğiz. Bir update süreci sertifikasyon gibi aşamaları gerektirdiği için belki 1 haftadan önce marketplace'te yer alamayacağını düşünürsek. Sanırım database'e metro style uygulamadan bağlanma hevesiniz bayağı bir azaldı. Gelelim son nedene.
  • Zaten şu anda istesenizde Windows 8 Metro Style tarafında SqlConnection sql = new SqlConnection(); yapamıyorsunuz :). Çünkü öyle bir API yok :D Zaten olsa da yapmamanız gerekir yukarıda bahsetmiş olduğum nedenlerden dolayı. ;)

Not : Yukarıda bahsettiğim nedenler uygulamanızın ana datasına erişimin nasıl yapılması ile ilgiliydi. Yani metro style uygulama ile ilgili bir takım user settinglerini localde saklayabileceğiniz yapılar zaten şu anda WinRT içerisinde mevcut.

Peki elimizde bir database var ve biz bu elimizdeki verileri nasıl dışarıya açacağız ? Cevap oldukça basit : Web Servis :) Peki web servise erişimi nasıl yapacağız ? Bunun da cevabı oldukça basit. Asenkron :) Asenkron işlemlerin yönetiminde de C# 5.0 ile beraber gelen async / await yapısını kullanıyor olacağız. Detaylı bilgi için buraya :)

Bu konsept aslında sırf Metro Style uygulamalar ile sınırlı değil. Eğer elinizde bulunan dataları çeşitli platformlarda kullanmanız gerekiyorsa araya mutlaka bir web servis katmanı alarak dış platformları data access katmanında soyutlamanız doğru bir hareket olacaktır ;)

Şimdi gelelim artık metro style uygulama geliştirme tarafına :)

GridView Kontrolü

Metro Style uygulama geliştirirken en sık kullanacağımız kontrollerden biri de GridView kontrolü. Elimizdeki verileri toplu şekilde göstermek istediğimizde içerisindeki elemanları Grid şeklinde gösterebilen bir kontrol GridView kontrolü. Evet çok fazla uzatmadan artık pratikte görmek için File=>New Project ve Blank Metro Style Application'ı seçerek boş bir uygulama yaratıyoruz.

Uygulama olarak bir restoranımız olduğunu düşünelim ve bu restoran için bir Windows 8 Metro Style uygulama geliştireceğimizi varsayalım. Restoran içerisinde satılan yiyecekler ile ilgili bilgiler ise web servis aracılığıyla geliyor olacak ve biz yemek listesini web servis içerisinde bulunan GetFoods metodu ile alacağız. Bu metot içerisinden dönen Food tipinin içeriği ise şu şekilde.

[sourcecode language="csharp"] public class Food { [DataMember] public string FoodName { get; set; } [DataMember] public decimal Price { get; set; } [DataMember] public string ImageUrl { get; set; } } [/sourcecode]

Şimdi ilk olarak bir GridView kontrolü alalım ve MainPage.xaml içerisine sahneye yerleştirelim.

[sourcecode language="xml"] <Page x:Class="App4.MainPage" IsTabStop="false" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App4" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <GridView x:Name="foodGrid" Margin="50,140,15,15"/> </Grid> </Page> [/sourcecode]

Şimdi sıra geldi datayı web servisten çekme kısmına. İlk olarak web servis referansını projemize ekliyoruz.

Advanced butonuna tıkladığımızda asenkron web servis erişiminin bulunduğu bölüme dikkat ;)

Şimdi sıra geldi web servis bağlantısına ve aldığımız dataları GridView kontrolüne bağlamaya. Bu işlemi sayfanın OnNavigated eventi içerisinde gerçekleştiriyor olacağız. OnNavigated metodu MainPage.xaml sayafasına navigasyon gerçekleştiğinde tetiklenmekte.

[sourcecode language="csharp"] protected async override void OnNavigatedTo(NavigationEventArgs e) { FoodService.ServiceClient client = new FoodService.ServiceClient(); ObservableCollection<Food> foodList = await client.GetFoodsAsync(); foodGrid.ItemsSource = foodList; } [/sourcecode]

Gördüğünüz gibi eğer daha önce XAML tabanlı bir platformda (Silverlight,WPF,Windows Phone vb...) geliştirme yaptıysanız kodlar sizin için oldukça tanıdık gelecektir. Windows 8 Metro Style tarafında da genel konsept aslında aynı. GridView kontrolünün ItemsSource propertysine elimizdeki listeyi veriyoruz.

Şimdi F5'e basarak uygulamamızı çalıştıralım ve uygulamamız nasıl görücek bir bakalım.

Sanki tam istediğimiz gibi olmadı :) Nesnelerimiz GridView kontrolü içerisinde göründü ancak bağladığımız Food tipinin sadece ismi ekrana yazıldı. Yani toString metodu çağrıldı.

Daha önce XAML tarafıyla uğraşanlar GridView içerisinde ItemTemplate yaratmadık diyeceklerdir. :)  Çünkü GridView şu anda içerisine verdiğimiz nesneleri nasıl göstereceği konusunda bilgi sahibi değil ;)

ItemTemplate yaratmanın 2 yöntemi var. İlki Visual Studio 2012 ile beraber templateları özelleştirme. Bir diğeri de Expression Blend içerisinden templateları özelleştirme.

Visual Studio 2012 içerisinde yapmak istediğimizde, designer görünümünde kontrole sağ tıkladığımızda tıpkı Expression Blend'de yaptığımız gibi kontrolün templatelarını özelleştirebilmekteyiz.

Bir diğer yöntem de Expression Blend kullanmak. Çünkü artık Expression Blend, Visual Studio 2012 kurulumuyla beraber bilgisayarımıza yüklenmekte ve rahatça kullanabilmekteyiz. Bunun için de değiştirmek istediğiniz sayfaya sağ tıkladığınızda "Open in Blend" 'e basarak ilgili sayfayı Expression Blend içerisinden editleyebilmekteyiz.

Ben eskiden kalma alışkanlıklarımdan dolayı tasarım tarafını daha çok Blend tarafında hallediyorum. Ancak yukarıda göstermiş olduğum gibi siz de isterseniz bu işlemleri Visual Studio 2012 içerisinden gerçekleştirebilirsiniz.

Şimdi geri dönelim konumuza :) GridView kontrolüne içerisindeki her bir nesneyi nasıl göstereceğini söyleyecektik. Bunun için de GridView içerisinde bir ItemTemplate yaratmamız gerekmekte. Biz ItemTempate olarak yemeğin resmini ve resmin altında da yemeğin adını yazıyor olacağız.

[sourcecode language="xml"] <Page x:Class="App4.MainPage" IsTabStop="false" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App4" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.Resources> <DataTemplate x:Key="FoodTemplate"> <Grid> <Image HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="250" Stretch="UniformToFill" Source="{Binding ImageUrl}"/> <Border BorderThickness="1" HorizontalAlignment="Left" Height="22.393" Margin="0,223.607,0,0" VerticalAlignment="Top" Width="250" Background="Black" Opacity="0.4"> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Margin="3,2,0,0" Text="{Binding FoodName}"/> </Border> </Grid> </DataTemplate> </Page.Resources> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <GridView x:Name="foodGrid" Margin="50,140,15,15" ItemTemplate="{StaticResource FoodTemplate}"/> </Grid> </Page> [/sourcecode] Şimdi uygulamamızı yeninden çalıştıralım ve neler değişti bir bakalım :)

Tamam kabul ediyorum uygulama biraz fazla akıl çelici oldu :) Ancak gördüğünüz gibi GridView içerisinde ItemTemplate'ı yaratarak istediğimiz şekilde nesneyi gösterebildik.

Data uygulamalarına giriş yaptığımız bu yazımızda sizlerle Windows 8 Metro Style uygulamalarda veri erişiminin nasıl yapılabileceğimizi ve en sık kullanılan data kontrollerinden olan GridView kontrolünü nasıl kullanabileceğimizi incelemiş olduk.

Daha önce  mevcut Windows 8 uygulamaları görenler "yahu bunun böyle gruplanmış halleri oluyor. Mesela zeytinyağlılar bir grupta, tatlılar bir grupta görünüyor" o nasıl oluyor diye sorabilirler :) Bu da diğer yazımızın konusu olsun ;)

Görüşmek Üzere,



Tüm Yönleriyle C# 5.0 Webineri Kaydı

Merhaba Arkadaşlar,

23 Şubat 2012 tarihinde Nedirtv Şubat Ayı özel Level 300 webinerleri kapsamından yapmış olduğum "Tüm Yönleriyle C# 5.0" webinerinin kaydını sizlerle paylaşıyorum.

Webiner süre olarak tahmin ettiğimden biraz daha uzun sürse de (yaklaşık 2 saat kadar :) ) faydalı bir webiner olduğunu düşünüyorum. :)

Webineri kayıt altına alan ve bana ulaştıran Bahtiyar Dilek'e teşekkür ederim.

Mart ayında ise Akdeniz Bölgesinde bir seminerde olacağım. Detaylar  çok yakında... :)

Webineri buradan download edebilirsiniz.

Görüşmek Üzere,



C# 5.0 & VB.NET 11.0 Async ve Await İle Kolaylaşan Asenkron İşlemler

Merhaba Arkadaşlar,

C# 5.0 & VB.NET 11.0 Asenkron Programlama 5N1K (Ne,nerede,ne zaman,nasıl,neden,kim)  isimli yazımızda sizlerle C# 5.0 ile beraber gelecek olan Asenkron Programlama yeniliklerini incelemeye çalışmıştık. O yazımızda dilsel yeniliklerden bahsetmek yerine daha çok konseptsel olarak Asenkron Programlama’nın ne olduğundan, neden gün geçtikçe daha da önem kazandığından ve hangi noktalarda bazı sorunlar yarattığından bahsetmiştik. Bu yazımızda ise artık teorik kısmı bir kenara bırakıp gelen yenilikleri kod üzerinden incelemeye çalışıyor olacağız.

Yazımız boyunca Netflix üzerinden girilen yıla göre çekilen filmleri getiren bir uygulama geliştiriyor olacağız. Uygulama içerisinde de Netflix’in sunmuş olduğu OData(Open Data Protocol) servisinden faydalanacağız.

İlk olarak uygulamamızı Netflix’ten filmleri senkron olarak getirecek şekilde geliştiriyor olacağız. Sonrasında aynı uygulamayı şu anda elimizde bulunan imkanlarla asenkron hale getireceğiz ve son olarakta aynı uygulamayı C# 5.0 ile beraber gelen async ve await keywordlerini kullanarak geliştirip, aralarındaki farkları incelemeye çalışacağız. O zaman hemen senkron olarak verileri getiren uygulamamızı geliştirmeye başlayalım !

Senkron Programlama

İlk olarak uygulamamızın arayüzünü tasarlayarak işe başlayalım. WPF tabanlı uygulamamız oldukça basit bir arayüze sahip olacak. Kullanıcıdan alacağımız yıla göre verileri getirme işlemine başlayacağız ve bir panel içerisine söz konusu filmler ile ilgili bilgileri dolduracağız. Bu nedenle hemen arayüz kısmını tasarlayalım ve sonrasında kodlama tarafına geçelim.

<Grid>   
<Label Content="Filmin Çekilme Senesi" Height="28" HorizontalAlignment="Left"
 Margin="6,12,0,0" VerticalAlignment="Top" />   
<TextBox Height="23" HorizontalAlignment="Left" Margin="136,14,0,0"
 Name="txtMovieYear" VerticalAlignment="Top" Width="120" />   
<Button Content="Ara" Height="23" HorizontalAlignment="Left"
 Margin="278,13,0,0" Name="btnSearch" VerticalAlignment="Top"
 Width="75" Click="btnSearch_Click" />    
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="0,46,0,0">         
<WrapPanel Name="resultsPanel" Orientation="Horizontal"/>    
</ScrollViewer>
 </Grid>

 

Evet yazımız boyunca uygulamamızın arayüzü yukarıda gördüğünüz gibi olacak. Bu nedenle artık yazımız boyunca arayüz kısmına hiç değinmeyeceğiz ;).

Şimdi gelelim uygulamanın arka plan kodlarına:)

Kod tarafını geliştirmeye başlamadan önce Netflix’in developerlara sunmuş olduğu OData servisi ile ilgili bir kaç detaya dikkat etmek gerekiyor. Bunlardan en önemlisi de servisin paging mantığıyla çalışması. Servis her çağrıda maksimum 20 film kaydı geri dönmekte. Bu yüzden servise yapılan çağrı sonucunda toplamda kaç film döneceğini belli olmadığından, servis herhangibir film dönmeyene kadar iterasyonu sürdürmemiz gerekiyor.

Şimdi ilk olarak uygulama boyunca kullanacağımız ve Netflix servisinden dönecek olan filmleri temsil edecek, Movie isimli sınıfı tasarlayalım ve sonrasında da yine servis çağrıları ve parsing işlemleri sırasında kullanacağımız web adreslerini ve isim alanlarını(namespace) tanımlayalım.

XNamespace xa = "http://www.w3.org/2005/Atom"
XNamespace xd = "http://schemas.microsoft.com/ado/2007/08/dataservices"
XNamespace xm = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; 
string query = "http://odata.netflix.com/Catalog/Titles?$filter=ReleaseYear eq" 
               +"{0}&$skip={1}&$top={2}&$select=Url,BoxArt";
class Movie
{             
 public string Title { get; set; }             
 public string Url { get; set; }             
 public string BoxArtUrl { get; set; }         
}

Yukarıda görmüş olduğunuz namespace tanımlamalarını Netflix’ten dönen cevabın parse edilmesi ve çalışma zamanında Movies tipinden birer nesne örneği haline çevrilmesi sırasında kullanıyor olacağız. Query ismini verdiğimiz değişkenimiz ise istekte bulunacağımız adresi tutmakta.

Sıra geldi yıl girişi yapıldıktan sonra iş akışını yöneteceğimiz metodu yazmaya. Yazımızın başında Netflix’in bir çağrıda maksimum 20 film döndüğünden bahsetmiştik. Bu nedenle bizim arka planda bir paging yapısı kurarak arka arkaya Netflix tarafına isteklerde bulunmamız gerekmekte ta ki Netflix’ten yaptığımız çağrı sonucunda hiç film kaydı gelmeyene kadar.

void LoadMovies(int year)         
{             
 resultsPanel.Children.Clear();             
 var pageSize = 10;             
 var imageCount = 0;             
 while (true)             
 {                 
 var movies = QueryMovies(year, imageCount, pageSize);                 
 if (movies.Length == 0) break;                 
 DisplayMovies(movies);                 
 imageCount += movies.Length;             
 }         
}

Gördüğünüz gibi basit bir sonsuz döngü oluşturduk ve 10’arlı gruplar halinde film bilgilerini bize getirecek olan istekleri yarattık. Her isteğin yanıtını aldıktan sonra bir sonraki 10’arlı grubun isteğini yaratıp Netflix’e gönderdik. Netflix’ten film dönmediğinde ise artık filmlerin sonuna geldiğimiz anlayarak sonsuz döngüyü sonlandırdık.

LoadMovies metodu içerisinde 2 farklı metot çağrısı icra edildiği görülmektedir. Bunlardan biri Netflix’e istekte bulunan ve Movie dizisi döndüren QueryMovies metodu, diğeri ise dönen Movies dizisini ekranda image olarak gösteren DisplayMovies metodudur.Öncelikle DisplayMovies metodunun geliştirmesini yapalım.

void DisplayMovies(Movie[] movies)

{            

 foreach (var movie in movies)             
 {                 
 var bitmap = new BitmapImage(new Uri(movie.BoxArtUrl));                 
 var image = new Image();                 
 image.Source = bitmap;                 
 image.Width = 110;                 
 image.Height = 150;                 
 image.Margin = new Thickness(5);                 
 var tt = new ToolTip();                 
 tt.Content = movie.Title;                 
 image.ToolTip = tt;                 
 var url = movie.Url;                 
 image.MouseDown += (sender, e) => System.Diagnostics.Process.Start(url);                 
 resultsPanel.Children.Add(image);             
 }         

Metot içerisinde gelen her bir Movie nesne örneği için bir BitmapImage yaratılmakta ve daha sonra arayüzdeki panel içerisine yaratılan Image’lar teker teker eklenmektedir. Netflix’e istekte bulunan QueryMovies metodu ise şu şekilde olacaktır.

Movie[] QueryMovies(int year, int first, int count)         
{             
 var client = new WebClient();             
 var url = String.Format(query, year, first, count);             
 var data = client.DownloadString(new Uri(url));             

 var movies =from entry in XDocument.Parse(data).Descendants(xa + "entry")                 
 let properties = entry.Element(xm + "properties")                 
 select new Movie                 
 {                     
 Title = (string)entry.Element(xa + "title"),
                  Url = (string)properties.Element(xd + "Url"),
 BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")                 
 };
              return movies.ToArray();         

Yazımız boyunca en çok değineceğimiz metodumuz QueryMovies metodu olacaktır. Çünkü senkron veya asenkron geliştirmenin başlayacağı nokta bu metoddur. Biz ilk olarak senkron bir şekilde çalışacak uygulama geliştireceğimiz için, WebClient tipi içerisinde bulunan DownloadString metodunu kullandık. Bu metot senkron olarak ilgili adrese gitmekte ve o anda istekte bulunan threadi bekletmektedir. Bu nedenle uygulamayı tamamlayıp çalıştırdığımızda göreceksiniz ki filmlerin yüklenmesi hem uzun sürecek hem de bu sırada uygulamanız responsive(cevap verebilir) olmayacaktır.

Şimdi son olarak search butonumuz için Click olay metodunu da (event handler’ını da) yazalım ve uygulamamızı çalıştıralım.

private void btnSearch_Click(object sender, RoutedEventArgs e)         
{             
 LoadMovies(Int32.Parse(txtMovieYear.Text));         
} 

Uygulamayı çalıştırdığınızda istediğiniz bir yılı girip arama yapmaya çalıştığınızda Ara butonuna basar basmaz uygulamanızın kilitlendiğini göreceksiniz. Tabi network bağlantınıza göre uygulamanın yaklaşık olarak 1 ile 2 dakika arasında cevap veremez durumda olması hiçte istenilen bir durum değildir !  Hele ki artık uygulamaların tabletlere taşınmasıyla beraber bu gibi durumlar kullanıcı deneyimi açısından bakıldığında oldukça kötü deneyimler doğurmaktadır.

Özellikle uygulamalarınızda yukarıdaki örneğimizdeki gibi network üzerinden çağrılar yapıyorsanız veya I/O işlemleri gibi uzun sürebilecek işlemler gerçekleştiriyorsanız yapmanız gereken bu işlemleri asenkron bir biçimde gerçekleştirmektir. Böylece çalışan ana threadi kilitlemeyerek işlemleri bir arka thread’e alacağız.

Şimdi gelin aynı uygulamayı asenkron bir biçimde çalışacak şekilde değiştirelim. Bu değiştirme sırasında C# 5.0 ile gelen asenkron programlama yeniliklerini değil elimizde mevcut olarak bulunan asenkron yapılarını kullanacağız. Böylece C# 5.0 ile beraber gelen yeniliklerin kıymetini daha da iyi anlayacağız. ;)

C# 5.0 Öncesi Asenkron Programlama

.NET Framework içerisindeki kütüphanelere baktığımızda, bazıları içerisinde işlemlerin asenkron olarak yapılmasını sağlayan metotlar bulunduğunu görürüz. Bu fonksiyonlar Async son eki ile bitmektedir. Hatta web servis referansı eklediğinizde, eğer asenkron metotların da eklenmesini seçerseniz adları Async ile biten ve asenkron olarak çalışan metotları görebilirsiniz.

Şu anda framework içerisinde bulunan asenkron metotların çalışması ise callback mantığına dayanmaktadır. İlgili metodu çağırmadan önce metodun çalışmasının sonlanmasından sonra çalışacak olan bir callback tanımlıyorsunuz. Daha sonrasında ise metot arka planda işlemlerini sürdürürken ana threadiniz kilitlenmiyor ve uygulamanız tepki verebilir durumda kalıyor. Metot çalışması sonlandıktan sonra da callback fonksiyonu çalışarak ilgili işlemlerinizi yapabiliyorsunuz.

Bizim yukarıda çağırmış olduğumuz DownloadString metodunun da .NET Framework içerisinde asenkron olarak çalışan DownloadStringAsync isimli versiyonu bulunmaktadır. Şimdi gelin QueryMovies isimli metodumuzu bu noktadan başlayarak değiştirelim.

void QueryMoviesAsync(int year, int first, int count, 
 Action<Movie[], Exception> processMovies)         
{             
 var client = new WebClient();             
 var url = String.Format(query, year, first, count);             
 client.DownloadStringCompleted += (sender, e) =>             
 {                 
 if (e.Error != null)                 
 {                     
 processMovies(null, e.Error);                     
 return;                 
 }                 
 var data = e.Result;                 

 var movies = from entry in XDocument.Parse(data).Descendants(xa + "entry")                     
 let properties = entry.Element(xm + "properties")                     
 select new Movie                     
 {                         
 Title = (string)entry.Element(xa + "title"),                         
Url = (string)properties.Element(xd + "Url"),                         
 BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")                     
 };                 
 processMovies(movies.ToArray(), null);             
 };             

 try             
 {                 
 client.DownloadStringAsync(new Uri(url));             
 }             
 catch (Exception ex)             
 {                 
 processMovies(null, ex);             
 }         
}

Gördüğünüz gibi öncelikle DownloadStringCompleted isimli DownloadStringAsync isimli metodun çalışmasının sonlanmasının ardından çağrılacak olan callback tanımını yaptık. Sonrasında ise DownloadStringAsync isimli metodu çağırdık.

Metotta yaptığımız değişikliklerden birisi de parametre olarak aldığımız processMovies isimli Action tipi. Bu Action delegate’i içerisinde ise Netflix servisine yapacağımız çağrıları tutuyor olacağız. Böylece her callback içerisinde ilgili çağrım kontrolleri yapılıyor olacak. QueryMoviesAsync metodunu çağırdığımız ve action tipindeki delegate’I parametre olarak geçirdiğimiz LoadMoviesAsync metodu ise şu şekilde olacaktır.

void LoadMoviesAsync(int year)         
{             
 resultsPanel.Children.Clear();             
 var pageSize = 10;             
 var imageCount = 0;             
 Action<Movie[], Exception> action = null;             
 action = (movies, ex) =>             
 {                 
 if (ex != null)                 
 {                     
 throw ex;                 
 }                 
 if (movies.Length > 0)                 
 {                     
 DisplayMovies(movies);                     
 imageCount += movies.Length;                     
 QueryMoviesAsync(year, imageCount, pageSize, action);                 
 }             
 };             
 QueryMoviesAsync(year, imageCount, pageSize, action);         
}

Şimdi uygulamayı çalıştırdığımızda ilk farkedeceğimiz değişiklik artık yılı girip ara butonuna bastığımızda uygulamanın kilitlenmeyeceğidir. Ayrıca uygulama asenkron olarak çalışacağından her 10’arlı grup çağrısında dönen filmler ekranda görünüyor olacaktır. Aslında baktığımızda yazımızın başında bahsettiğimiz kullanıcı deneyimi konusunda oldukça iyi bir ilerleme kaydettik.

Şimdi developer açısından baktığımızda, bakarsak senkron olarak yazdığımız kodu neredeyse tamamen değiştirdiğimizi görebiliriz. Bunun yanında yaptığımız callback tanımlamaları da metodumuzu oldukça uzattı. Eğer Netflix servisi filmlerin  fotoğraflarını, detaylarını ve film listesini ayrı bir adresten sunsaydı, yazacağımız metot taslak olarak şu şekilde olacaktı.

WebClient filmClient = new WebClient();             
filmClient.DownloadStringCompleted += (sender, e) =>                 
{                     
 WebClient imageClient = new WebClient();                     
 imageClient.DownloadStringCompleted += (_sender, _e) =>                         
 {                             
 WebClient detailClient = new WebClient();                             
 detailClient.DownloadStringCompleted += (__sender, __e) =>                                 
 {                                 
 };                             
 detailClient.DownloadStringAsync(new Uri("Detail Getirme URI'si"));                         
 };                     
 imageClient.DownloadStringAsync(new Uri("Image Getirme URI'si"));                 
};
             
filmClient.DownloadStringAsync(new Uri("Film Listesi Getirme URI'si"));

Gördüğünüz gibi asenkron çağrılarımız arttıkça callback’lerimizin de sayısı artmaktadır. Bu durum da metot içeriklerinin oldukça büyümesine ve metotlar üzerinde kontrolü kaybetmemize sebep olarak bakımı oldukça zorlaştırmaktadır.

Ayrıca bir uygulamadaki bazı kısımları asenkron çalıştırmak için neredeyse tüm kodu değiştirdik. Daha da önemli olan uygulama geliştirme mentalitemizi değiştirdik. Açıkcası asenkron uygulama geliştirirken Dekleratif Programlama(bir işi yaparken nasıl yapacağından çok ne yapacağına odaklanmak)’dan oldukça uzaktaydık. Bu noktada sanırım asenkron kod yazmak bu kadar zor olmamalıydı dediğinizi duyar gibiyim :) Öyleyse gelin artık C# 5.0 tarafına geçelim ;)

C# 5.0 ile Asenkron Programlama

C# 5.0’ın teması önceki yazımızda da bahsettiğimiz gibi asenkron programlama. Bu sürümle beraber asenkron işlemlerin orkestrasyonunun artık compiler tarafına bırakılması amaçlanmakta. Böylece de artık senkron ile asenkron programlama arasındaki development farkının minimuma indirilmesi hedeflenmekte. Peki nasıl mı ?

C# 5.0 ile beraber artık hayatımıza async ve await isminde iki keyword giriyor olacak. Bu keywordlerden ilki olan async, asenkron çağrı yapılan metotların işaretlenmesi amacıyla kullanılmaktadır. Eğer bir metodunuz içerisinde asenkron bir çağrıda bulunuyorsanız ve bu çağrıdan dönen sonucun uygulama tarafına dönme işlemlerini compiler tarafına bırakmak istiyorsanız o metodun başına async keywordünü koymanız gerekmektedir. Böylece compiler async keywordünü gördüğü metotlarda asenkron çağrı yapıldığını algılayıp, çağrı yapılan yerlerde gerekli yapıları ve işleri kendisi arka planda yaratmaktacak ve yürütmektecektir.

Await keyword’ünü ise asenkron işlemleri çağırma sırasında kullanmaktayız. Asenkron çağrının yapıldığı kısımda await kullanarak bu ifadenin devamında bulunan kod bloğunun asenkron işlemin sonucunda işletilecek bölüm olduğunu compilera belirtmekteyiz.

Şimdi yavaş yavaş C# 5.0 ’a giriş yapalım. İlk olarak yapmamız gereken şu anda CTP3 sürümünde olan Visual Studio Async’i kurmak. Buradaki adresten ilgili setup dosyasını indirip kurulumu yapmanız gerekmekte. Kurulumu yaptıktan sonra çeşitli platformlardaki uygulamalarınızda kullanmanız için gelen farklı kütüphaneleri Documents\Microsoft Visual Studio Async CTP\Samples klasörü içerisinde bulacaksınız. Biz .NET uygulamalarında kullanacağımız AsyncCtpLibrary.dll’ini projemize öncelikle referans olarak ekliyor olacağız.

İlk olarak değişikliğimize WebClient tipi içerisinde bulunan DownloadStringAsync metodu ile başlayacağız. Yukarıda bahsetmiş olduğum kütüphaneyi proje referans olarak eklediyseniz WebClient tipi içerisine Task<string> tipini döndüren DownloadStringTaskAsync isimli metodun extension metot olarak eklendiğini görebilirsiniz. Peki bu metodun anlamı ne ?

Task tipi bildiğimiz gibi .NET 4.0 ile beraber paralel programlama yenilikleri kapsamında  .NET Framework içerisine eklendi. En basit anlamda söylememiz gerekirse Task tipi devam etmekte olan asenkron bir işlemi temsil etmektedir. Bu nedenle aslında baktığımızda Task tipi asenkron operasyonların yönetimi esnasında kullanılacak en öncelikli tiptir. İşte bu sebepten dolayı await ifadesi Task tipi veya void dönüş tipine sahip işlemler ile kullanılabilmekte yani compiler Task,Task<T> veya void dönüş değerine sahip asenkron işlemlerin yönetimini yapabilmektedir.

async Task<Movie[]> QueryMoviesAsync(int year, int first, int count)         
{             
 var client = new WebClient();             
 var url = String.Format(query, year, first, count);             
 var data = await client.DownloadStringTaskAsync(new Uri(url));             
 var movies =from entry in XDocument.Parse(data).Descendants(xa + "entry")                 
 let properties = entry.Element(xm + "properties")                 
 select new Movie                 
 {                     
 Title = (string)entry.Element(xa + "title"),                     
 Url = (string)properties.Element(xd + "Url"),                     
 BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")                 
 };             
 return movies.ToArray();         

Yukarıda QueryMovies metodumuzun C# 5.0 ‘a göre güncellenmiş halini görmekteyiz. İlk olarak yukarıda bahsettiğimiz gibi WebClient tipinin DownloadStringTaskAsync metodunu kullandık. Daha sonra bu metodu çağırmadan önce await keywordünü kullanarak bu metot çağrımından sonra bulunan ifadelerin artık callback içerisinde bulunmasını ve asenkron işlem tamamlandıktan sonra işletilmesi gerektiğini compilera belirttik.

Daha sonra ise metodumuz içerisinde asenkron bir çağrımda bulunduğumuzdan dolayı QueryMovies isimli metodun başına async modifier’ını koyduk. Böylece compilera bu metot içerisinde asenkron bir çağrım olduğunu ve await ifadesi gördüğü kısımlarda gerekli işlemleri yapmasını bildirdik.

Son olarak ise metodumuzun dönüş tipini Task<Movies[]> şeklinde değiştirdik. Böylece LoadMovies metodu içerisinden asenkron olarak bu metodun çağrımını gerçekleştirebileceğiz. Metodumuzun ismininde QueryMoviesAsync olarak değiştiğini farketmişsinizdir. Bunun nedeni ise isimlendirme kurallarına uygun bir şekilde geliştirme yapmamızdır. Asenkron olarak çalışan metotların isimlerinin sonuna Async koyarak bu metotları kullanan diğer developerların da metodun asenkron olarak çalıştığını anlaması kolaylaşacaktır.

Şimdi gelelim LoadMovies isimli metodumuza. Aslında bu metodumuz üzerinde de hemen hemen yukarıda yapmış olduğumuz değişiklikleri yapıyor olacağız.

async void LoadMoviesAsync(int year)         
{             
 resultsPanel.Children.Clear();             
 var pageSize = 10;             
 var imageCount = 0;             
 while (true)             
 {                 
 var movies = await QueryMoviesAsync(year, imageCount, pageSize);                 
 if (movies.Length == 0) break;                 
 DisplayMovies(movies);                 
 imageCount += movies.Length;             
 }         

Yukarıda gördüğünüz gibi kodumuz aslında neredeyse senkron kod ile aynı. Sadece metodumuzun başına async modifierını koyduk ve metot içerisinde bir asenkron metot çağrımı olacağını compiler’a bildirdik. Daha sonra ise biraz önce yazmış olduğumuz QueryMoviesAsync metodunu çağrımından önce await kullanarak metot çağrımından sonra gelen ifadelerin artık metot işletiminden sonra işletilmesi gerektiğini compilera bildirdik. Bir de tabi ki yine asenkron çalışan bir metot geliştirdiğimiz için metodumuzun ismini LoadMoviesAsync olarak değiştirdik.

Async & Await ile Kolaylaşan Asenkron İşlemler

Evet arkadaşlar gördüğünüz gibi C# 5.0 ile beraber gelen async ve await modifierları ile artık senkron veya asenkron çalışan kodlar arasında çokta fazla fark kalmıyor. Hemen hemen 2-3 değişiklik ile senkron olarak çalışan kodumuzu asenkron olarak çalışacak duruma getirebiliyoruz. Aşağıdaki görsellerde şimdiye kadar yazmış olduğumuz senkron, asenkron ve C# 5.0 asenkron kodlarını ve aralarındaki farklılıkları görebilirsiniz.

C# 5.0’ın temasının asenkron programlamayı senkron programalama kadar basit yapmak olduğunu yeniden hatırlarsak bunu başarıyla gerçekleştirdiğiniz sanırım yukarıdaki tablodan çıkarabiliriz.

Asenkron programlama yeniliklerini özetlememiz gerekirse;

Temel olarak C# 5.0 ile dile async ve await keywordleri gelmektedir. Bu keywordlerden async bir metot modifier olarak kullanılırken yazmış olduğunuz  metot içerisinde asenkron bir işlem yapıldığını compilera bildirmekte. async modifier’ına sahip olan metotların dönüş tiplerinin void,Task veya Task<T> tiplerinden birinin olması gerektiğini aklımızda bulundurmamızda yarar var.

Await ifadesi ise asenkron yürütülecek olan bir işlemin compilera bildirilmesinde ve bu ifadenin altında bulunan ifadelerin asenkron işlemin sonucunda işletileceğini compilera bildirmekte. Böylece compiler arka planda kurduğu callback yapısına bizim kodlarımızı dahil etmekte.

Bu noktaya kadar farkettiyseniz .NET 4.5 ile beraber gelecek olan hiçbir tipten bahsetmedik. Bunun nedeni ise C# 5.0’ın tamamen .NET Framework içerisinde bulunan mevcut tipleri kullanarak tüm iş yükünü üzerine alması. Belki de bu gelen yeni özelliğin bir dil özelliği olduğunu en iyi açıklayan argümanımız bu ;)

C# 5.0 ile gördüğünüz gibi yine oldukça güzel özellikler gelmekte. Async özelliğinin yukarıda bahsetmiş olduğumuz kullanımı dışında daha farklı kullanımlarını da ilerleyen yazılarımızda inceliyor olacağız. Bir sonraki yazımızda görüşmek üzere, C# ile kalın… Not 1: Uygulama Visual Studio Async CTP3 ile geliştirilmiştir. Yazımız boyunca yazdığımız C# kodlarını aşağıdan indirebilirsiniz. AsyncStuff


C# 5.0 Paralel İşlemlerin Yönetimde Async & Await Kullanımı ve Task Combinator Metotları

Merhaba Arkadaşlar,

Önceki yazılarımızda sizlerle C# 5.0 ile gelecek olan Asenkron Programlama yeniliklerini gerek konseptsel olarak gerekse örnekler üzerinde uygulamalı olarak incelemiştik. Bu yazımızda ise C# 5.0 gelen Asenkron Programlama yeniliklerinin bir diğer kullanım alanını inceliyor olacağız.

.NET Framework 4.0’ın duyurulmasıyla beraber Framework içerisinde bildiğimiz gibi Parallel Programming konsepti kapsamında pek çok yenilik getirildi. Bu gelen yeniliklerle beraber artık uygulamalarımız içerisinde paralel olarak çeşitli süreçler çalıştırabilmekteyiz ve yönetimlerini de biraz zorlanarakta olsa sağlayabilmekteyiz. .NET Framework 4.0 içerisindeki Parallel Programlama yapısını inceldiğimizde bildiğimiz üzere herşeyin temelinde Task tipinin yattığını görmekteyiz. Aslında Task tipini incelediğimizde Paralel veya Asenkron bir işlemi temsil etmek için mükemmel bir tip olduğunu söyleyebiliriz. Bunun nedeni ise yapılan işlem ile ilgili tüm bilgileri içerisinde bulundurması. Asenkron işlemin durumunun kontrol edilmesi, içerisinde oluşan bir exception’ın elde edilmesi, asenkron işlemin tamamlanmasından hemen sonra çalıştırılacak olan işlemlerin belirtilmesi gibi tüm işlemleri bu tip üzerinden gerçekleştirebilmekteyiz. (Sanırım C# 5.0 ile gelen yeniliklerin nerede devreye gireceğini yavaş yavaş tahmin etmeye başladınız :) ) Evet bu yazımızın konusu Paralel işlemlerin yönetiminde Async ve Await keywordlerinin kullanımı olacak.

C# 5.0 ile beraber gelen yenilikleri uygulamalı olarak incelediğimiz yazımızdaki kısımları hatırlamamız gerekirse await keywordü sayesinde Task,Task<T> veya void dönüş tipine sahip olan asenkron işlemlerinin yönetimini sağlayabiliyorduk. Bu nedenle paralel işlemlerini yönetimini de aynı şekilde sağlayabileceğimizi düşünebiliriz. Örneğin,  Task.Factory.StartNew  metodunu kullanarak bir işlemi arka planda işletmeye başladığımızda StartNew metodu bize Task tipinde bir nesne örneği döndürmekte. İşte tam da bu noktada Async ve Await keywordleri devreye girmekte ve bu şekilde paralel işlemlerin yönetimini kolay bir şekilde sağlayabilmekteyiz.

Şimdi hemen Visual Studio üzerinden bir WPF uygulaması yaratalım ve ilgili AsyncCTPLibrary dll’ini projemize referans olarak ekleyelim ve uygulamamızı geliştirmeye başlayalım.

Uygulamamızın arayüzü oldukça basit olacak ve sadece 1 adet Textblock ve Button’dan oluşuyor olacak.

<Window x:Class="AsyncAwaitParallel.MainWindow" 
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
Title="MainWindow" Height="350" Width="525">     
<Grid>         
<Button Content="Compute" Height="23" HorizontalAlignment="Left" 
 Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" 
 Width="75" Click="button1_Click" />         
<TextBlock HorizontalAlignment="Left" Margin="12,41,0,0” 
Name="txtResult"VerticalAlignment="Top" />     
</Grid> 
</Window>

Uygulama içerisinde ise aslında bizim için hiçbir anlamı olmayacak ancak CPU’yu oldukça yoran ve maksimum düzeyde çalıştıran işlemler gerçekleştireceğiz. Bu işlemler de Math tipi içerisinde bulunan Sqrt, Sin ve Tan fonksiyonları olacak. Basit bir for döngüsü içerisinde bu fonksiyonları paralel olarak arka planda çalıştırıp elde ettiğimiz değerleri topluyor olacağız. Daha sonra ise topladığımız değerleri de ekrandaki textbox’a yazdırıyor olacağız.

private void button1_Click(object sender, RoutedEventArgs e)
{
 double SqrtSum = 0, SinSum = 0, TanSum = 0;
 Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {                         
 SqrtSum += Math.Sqrt(i);
 }                 
 }).ContinueWith((t) => {
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum)});

 Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SinSum += Math.Sin(i);
 }
 }).ContinueWith((t) => {
 txtResult.Text += String.Format("SinSum = {0}", SinSum)});             

 Task.Factory.StartNew(() =>
 {                 
 for (int i = 0; i < 50000000; i++)
 {
 TanSum += Math.Tan(i);
 }
 }).ContinueWith((t) => {
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum)});
}

Evet gördüğünüz gibi herbir CPU yoğun işlemi Task.Factory.StartNew metodunu kullanarak arka plana aldık. Böylece hem arayüzümüzün cevapsız kalmasının önüne geçerek uygulamamızın donmamasını sağladık. Hem de arka planda CPU’nun etkin bir biçimde tüm çekirdeklerini kullanmasını sağladık. Şimdi isterseniz uygulamamızı çalıştıralım.

Upps !!! İşte karşımızda asenkron işlem çalıştırma bunalımı :) Arka planda bir işlem gerçekleştiriyorsunuz ve bunun sonucu ekrana yazamıyorsunuz çünkü ekranda kullanacağımız kontrol başka bir thread’e ait :). Neyse şimdilik bu kısmı pas geçelim ve MessageBox.Show kullanarak sonuçları ekran görüntüleyelim. Ancak yazımızın ilerleyen bölümlerinde bu noktaya değiniyor olacağız. ;) MessageBox.Show metodunu kullarak elde ettiğimiz görüntüler ise şu şekilde.

Uygulamayı çalıştırdığımızda işlemler çeşitli sıralarda arka planda çalıştırıldı ve Task sona erdiğinde sonucu MessageBox kullanarak görüntüledik. Ancak baktığımızda yine önceki yazılarımızda da değindiğimiz Callback tabanlı bir yaklaşım izledik. ContinueWith metodunu kullanarak içerisinde lambda ifadesi yardımıyla Task’ın sonlamasından sonra çalışacak işlemleri belirttik. Peki ya task bittikten sonra başka bir taskın çalışmasını isteseydik ve o task’ta bittiğinde başka bir taskı başlatsaydı :)

Task.Factory.StartNew(() => { }).ContinueWith((t) =>
{
Task.Factory.StartNew(() => { }).ContinueWith(k =>
 {                         
 });                 
});

Yukarıda gördüğümüz gibi yine iç içe girmiş ifadelerle karşılaşıyor olacağız. Aslında yine başa döndük diyebiliriz. Asenkron programlamada da aynı bunalıma düşmüştük ve C# 5.0 ile bu bunalımdan hızlıca çıkmıştık. Öyleyse hemen C# 5.0 ile uygulamamızı yeniden düzenleyelim. Öncelikle metodumuz içerisinde Task tipi üzerinden paralel işlemlerin yönetimini yapacağımızdan dolayı metodumuzun başına async modifier’ını ekliyoruz.

Sonrasında ise ilk olarak her Task.Factory.StartNew metodunu kullandığımız ifadelerin başına await keyword’ünü yerleştirelim (StartNew metodu Task tipini döndürdüğünden dolayı await ifadesini kullanabilmekteyiz.) ve sonrasında o işlem sonucunda elde ettiğimiz değerleri ekrana yazdıralım.

private async void button1_Click(object sender, RoutedEventArgs e)
{
 double SqrtSum = 0, SinSum = 0, TanSum = 0;
 await Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SqrtSum += Math.Sqrt(i);
 }
 });
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum);

 await Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SinSum += Math.Sin(i);
 }
 });
 txtResult.Text += String.Format("SinSum = {0}", SinSum);
 await Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 TanSum += Math.Tan(i);
 }
 });

 txtResult.Text += String.Format("\nTanSum = {0}", TanSum);
}

Uygulamayı çalıştırdığımızda sonuçların ekranda göründüğünü görüyor olacağız.

Ancak uygulamayı çalıştırdığımızda sonuçların ekranlara yazılma sırasının hep sqrt,sin ve tan şeklinde olduğunu farketmişinizdir. Acaba gerçekleştirdiğimiz kullanım kodumuzunun paralel olarak işletilmesinin önüne mi geçiyor ? Şimdi gelin isterseniz await ifadesinin kullanımını bir hatırlayalım.

Compiler, await keyword’ünü gördüğü noktadan itibaren mevcut ifadenin sonrasında işletilecek olan ifadeleri bir devam kodu olarak işaretlemekte. Yani mevcut işlem sonlandırıldıktan sonra diğer ifadeleri işletmekte. Böyle olunca da aslında önce Sqrt işlemi gerçekleşmekte sonrasında da Sin ve Sin işleminden sonra da Tan hesaplaması yapılmakta. Peki kodumuzu nasıl paralel olarak işleteceğiz. Aslında sadece 1-2 ufak değişiklik yapmamız bizim için yeterli.

private async void button1_Click(object sender, RoutedEventArgs e)
{
 double SqrtSum = 0, SinSum = 0, TanSum = 0;
 Task t1 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SqrtSum += Math.Sqrt(i);
 }
 });
 Task t2 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SinSum += Math.Sin(i);
 }
 });
 Task t3 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 TanSum += Math.Tan(i);
 }
 });
 await t1;
 await t2;
 await t3;
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum);
 txtResult.Text += String.Format("SinSum = {0}", SinSum);
 txtResult.Text += String.Format("\nTanSum = {0}", TanSum);
}

Yukarıdaki gibi kodumuzu değiştirirsek aslında istediğimiz paralel işletimi sağlamış olmaktayız. Çünkü öncelikle işlemleri arka planda başlatıyoruz ve sonrasında await ifadelerini kullanıyoruz , böylece de işlemlerin sona ermesinden sonra oluşan değerleri ekrana yazdırıyoruz. Ayrıca ekrana yazdırma işlemini artık başka bir thread içerisinde yapmadığımızdan dolayı doğrudan arayüz threadi üzerinden işlemleri gerçekleştirmemiz sayesinde yukarıda düşmüş olduğumuz asenkron işlem bunalımına burada düşmemiş oluyoruz ;)

Evet arkadaşlar gördüğünüz gibi paralel işlemlerin yönetimini de async ve await keywordleri ile sağlayabilmekteyiz. Eğer taskları aynı anda başlatmak isterseniz yukarıdaki gibi bir kullanım gerçekleştirebilirsiniz. Bunun yanında eğer tasklarınız birbirine bağımlıysa ve biri bittikten sonra diğerinin başlaması gerekiyorsa da bir önceki kullanım tam size göre olacaktır.

Taskları teker teker yönetmenin yanında toplu olarak yönetmemizi sağlayan Task Combinators adını verdiğimiz metotlar hali hazırda .NET Framework içerisinde bulunmakta. Bu metotlar uygulamamız içerisinde yarattığımız tüm taskların işletilmesinin sona ermesini beklemek gibi işlemler gerçekleştirebilmekte. Ancak bu metotlar bool değer döndürdüğü için örnek olarak tüm taskların işletiminin sona erdikten sonra bir takım işlemleri gerçekleştirmemiz oldukça sıkıntılı olmakta. Bu nedenle .NET Framework 4.5 ile Task tipi içerisine dönüş tipi Task olan Combinator metotlar ekleniyor olacak. Şimdi gelin Task Combinator metotlarını kısaca inceleyelim.

Task Combinators

Not : Task Combinators olarak bahsettiğimiz metotlar eğer Visual Studio 2010 kullanıyorsanız projenize referans olarak eklemiş olduğumuz AsyncCTPLibrary  dll’i içerisindeki TaskEx tipi içerisinde bulunmakta. Bunun nedeni makinanızda .NET Framework 4.0 kurulu olduğundan dolayı mevcut metotları bozmamak. Ancak Visual Studio 2011 kullanıyorsanız .NET Framework 4.5 yüklü olacağından dolayı ilgili metotlar Task tipi içerisinde olacaktır ve TaskEx tipi burada bulunmayacaktır. Biz örneklerimizi Visual Studio 2010 üzerinden geliştirdiğimiz için TaskEx tipi üzerinden ilerliyor olacağız.

TaskEx tipi içerisinde bulunan static metotları incelediğimizde parametre olarak IEnumerable<Task> tipinden parametreler alan WhenAll, WhenAny isimli Task tipinden bir nesne örneği döndüren metotları görmekteyiz. Bu metotlar önceden de bildiğimiz gibi yaratmış olduğumuz taskların sonlanmasını beklemekte. Ancak Task tipinden bir nesne örneği döndürdüğünden dolayı async ve await keywordlerini kullanarak tüm taskların bitiminden sonra istediğimiz işlemleri gerçekleştirebilmekteyiz.

private async void button1_Click(object sender, RoutedEventArgs e)
{
 double SqrtSum = 0, SinSum = 0, TanSum = 0;
 Task t1 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SqrtSum += Math.Sqrt(i);
 }
 });
 Task t2 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SinSum += Math.Sin(i);
 }
 });
 Task t3 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 TanSum += Math.Tan(i);
 }
 });

 await TaskEx.WhenAll(t1, t2, t3);
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum);
 txtResult.Text += String.Format("SinSum = {0}", SinSum);
 txtResult.Text += String.Format("\nTanSum = {0}", TanSum);
}

Yukarıdaki gerçekleştirime baktığımızda WhenAll metoduna yaratmış olduğumuz taskları parametre olarak geçirerek tüm tasklar içerisindeki hesaplama işlemlerinin bitmesini bekledik ve await keyword’ü sayesinde hesaplama işlemi sona erdikten sonrada ilgili sonuçları ekrana yazdırmış olduk.

TaskEx tipine baktığımızda pek çok farklı Combinator metot bulunmakta. Bu combinator metotların yanında bir de Delay isimli bir metot olduğunu görmekteyiz. Bu metot ta aslında Task tipinden bir nesne örneği döndüren Thread.Sleep metodunun ta kendisi :) Ancak tabi ki Task tipi işin içerisine girdiğinden dolayı await keywordü ile çok kolay bir şekilde bekletme işi sona erdikten sonra istediğimiz işlemleri gerçekleştirebiliriz. Örneğin tüm tasklar sona erdikten her bir sonucu 1’er saniye ara ile ekrandaki kontrolümüze yazabiliriz.

private async void button1_Click(object sender, RoutedEventArgs e)
{
 double SqrtSum = 0, SinSum = 0, TanSum = 0;
 Task t1 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SqrtSum += Math.Sqrt(i);
 }
 });
 Task t2 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 SinSum += Math.Sin(i);
 }
 });
 Task t3 = Task.Factory.StartNew(() =>
 {
 for (int i = 0; i < 50000000; i++)
 {
 TanSum += Math.Tan(i);
 }
 });

 await TaskEx.WhenAll(t1, t2, t3);
 txtResult.Text += String.Format("SqrtSum = {0}", SqrtSum);
 await TaskEx.Delay(1000);
 txtResult.Text += String.Format("SinSum = {0}", SinSum);
 await TaskEx.Delay(1000);
 txtResult.Text += String.Format("\nTanSum = {0}", TanSum);
}

Gördüğümüz gibi Delay metodu 1 saniye sürecek olan bir Task yarattı ve biz de await ile bu task’ın sona ermesinden sonra çalışacak ifade olarak ekrana sonucu yazdıracak olan ifadeyi bildirdik.

Evet arkadaşlar gördüğünüz gibi aslında async ve await keywordlerini pek çok farklı noktada kullanabilmekteyiz. Ancak baktığımızda aslında Task tipini temel olarak alan işlemlerde await keywordünü kullanabildiğimizi gördünüz. Bu nedenle de Framework içerisinde bulunan metotlarda da Task tabanlı bir altyapıya geçiş yapılmakta. Bu şekilde programlama dili tarafından sunulan kolaylıklar framework ile de desteklenmekte. Await kullanımı için aslında bir takım belirli prensipler bulunmakta. Await kullanacağımız ifadenin tipinin Task,Task<T> veya void olması gibi. Bu şekilde kullanımlara aslında Awaitable Pattern adını veriyoruz. Yani gerçekleştirdiğiniz altyapı awaitable pattern’a uygun ise kolaylıkla async & await ifadesini kullanabilirsiniz. ;)

Umarım sizler için faydalı bir makale olmuştur.

Bir sonraki yazımızda görüşmek üzere,

Hoşçakalın

Not : Uygulama Visual Studio Async CTP3 ile geliştirilmiştir.



Asenkron WCF Servis Çağrımlarında C# 5.0 ile Gelen Async & Await Kullanımı

Merhaba Arkadaşlar,

C# 5.0 ile gelecek olan Asenkron Programlama yeniliklerinin önemli bir kısmını önceki yazılarımızda inceledik. Şu ana kadar farkettiyseniz asenkron programalama yaptığımız belki de en önemli noktalardan biri olan bir konuya hiç değinmedik. Bu konu da asenkron web servis çağrıları. Özellikle Silverlight ya da Windows Phone uygulamaları gibi istemci tarafında çalışan uygulamalar geliştiriyorsanız asenkron web servis çağrısı yapmamak neredeyse imkansız. Tabi aslında asenkron servis çağrıları sadece bu teknolojiler ile sınırlı değil. WPF veya ASP.NET tabanlı web uygulamalarımızda da asenkron web servis çağrıları gerçekleştirebilmekteyiz.

Asenkron olarak  yaptığımız web servis çağrılarının yanıtını almak için bildiğiniz gibi callback yöntemini kullanmaktayız. Yani asenkron olarak web servis çağrısını yaptıktan sonra kontrol bize geri dönmekte ve web servisten yanıt döndükten sonra callback olarak bildirdiğimiz kod parçası işletilmekte. Bu yöntemin dezavatajlarını, kodumuzu nasıl düzensiz hale getirdiğini ve uygulamamızın ölçeklenebilirliğini nasıl düşürdüğünü önceki yazılarımızda incelemiştik. İşte tam bu noktada web servis çağrılarında C# 5.0 ile gelen async & await ifadelerinin kullanılamayacağını sorduğunuzu duyar gibiyim :)

Bu noktada aslında web servis proxy’lerinin awaitable pattern’a göre oluşturulması bizim async & await kullanmamız için yeterli. Örnek vermek gerekirse bir web servis metodu int bir değer döndürüyorsa dönüş tipinin Task<int> tipi olması o metodun asenkron olarak işletilmesi ve yanıtının async ve await kullanılarak alınması için yeterli olmakta.

Şu anda Visual Studio içerisinde baktığımızda Add New Service Reference penceresi içerisinde yukarıda bahsettiğimiz biçimde proxy kodu üretecek bir mekanizma bulunmamakta. Bu tabi ki hiç olmayacak anlamına gelmiyor. Gelecek Visual Studio sürümünde bu özellikle geliyor olacak. Peki biz bu özelliği beklemeden asenkron web servis çağrılarımızı async ve await ile nasıl yönetebiliriz ?

Öncelikle basit bir WCF servis metodu yazalım ve ardından da bu metodumuzu WPF istemcisi tarafından çağıralım.

Yazacağımız web servis metodu oldukça basit olacak ve parametre olarak aldığı 2 sayıyı toplayarak geri döndürecek. Ancak bu işlemi yapmadan önce biz işlemi 3 saniye bekleteceğiz o şekilde metottan değeri geri döndüreceğiz. Böylece web servis metodumuz bizim için asenkron çağrı yapmak için anlamlı hale gelecek. ;) Aksi takdirde zaten 2 sayısı toplayan bir web servis metodu için asenkron çağrı yapamaya çok ta gerek yok sanki :)

İlk olarak WCF servisimizin Service Contract’ının geliştirelim.

[ServiceContract]
public interface IMathService
{
 [OperationContract]
 double Add(double x, double y);     
}

Sıra geldi contract'ını belirlediğimiz WCF servisimizin implementasyonuna.

public class MathService : IMathService 
{    #region IMathService Members
   
 public double Add(double x, double y)
 {
 Thread.Sleep(3000);
 return x + y;
 }    #endregion
}

Gördüğünüz gibi metot sonucunu dönmeden önce Thread.Sleep metodunu kullanarak işlemin 3 saniye beklemesini sağladıktan sonra 2 sayıyı toplayıyıp servis metodundan geriye döndük.

WCF tarafında son olarak ise servisimizin konfigürasyonunu yapıyoruz ve servisi http://localhost/MathService endpoint’i üzerinde dışarıya açıyoruz.

<system.serviceModel>     
<services>       
<service name="MathServiceLib.MathService">         
<endpoint address="" binding="wsHttpBinding" 
contract="MathServiceLib.IMathService">           
<identity>             
<dns value="localhost" />           
</identity>         
</endpoint>         
<endpoint address="mex" binding="mexHttpBinding" 
contract="IMetadataExchange" />         
<host>           
<baseAddresses>             
<add baseAddress="http://localhost/MathService" />           
</baseAddresses>         
</host>       
</service>    
 </services>     
<behaviors>       
<serviceBehaviors>         
<behavior>           
<serviceMetadata httpGetEnabled="True"/>           
<serviceDebug includeExceptionDetailInFaults="False" />         
</behavior>       
</serviceBehaviors>    
 </behaviors>   
</system.serviceModel>
Şimdi sıra geldi WPF istemci uygulamamıza. İstemci uygulamamızın arayüzü de şu şekilde olacak.
<Grid>    
<Label Content="1. Sayı" Height="28" HorizontalAlignment="Left"
 Margin="12,12,0,0" VerticalAlignment="Top" />    
<Label Content="2. Sayı" Height="28" HorizontalAlignment="Left"
 Margin="12,44,0,0" VerticalAlignment="Top" />    
<TextBox Height="23" HorizontalAlignment="Left" Margin="81,12,0,0"
 Name="txtSayi1" VerticalAlignment="Top" Width="120" />    
<TextBox Height="23" HorizontalAlignment="Right" Margin="0,46,302,0"
 Name="txtSayi2" VerticalAlignment="Top" Width="120" />    
<Button Content="Topla" Height="23" HorizontalAlignment="Left"
 Margin="12,78,0,0" Name="btnTopla" VerticalAlignment="Top"
  Click="btnTopla_Click" Width="75" />    
<Label  Height="28" HorizontalAlignment="Left" Margin="106,73,0,0"
 Name="lblSonuc" VerticalAlignment="Top" /> 
</Grid>
 

Arka plan kodlarına geçmeden son olarakta WCF servisimizin referansını WPF projemize ekliyoruz. Servis referansını eklerken de Add New Service Reference penceresindeki Advanced butonuna tıklayıp açılan pencere içerisindeki asenkron çağrım kodlarının da üretilmesini sağlayan checkbox’ı seçiyoruz.

Yukarıdaki gibi asenkron web servis çağrım kodlarını ürettiğimizde ilgili proxy tipi içerisinde AddAsync isimli bir metodun olduğunu göreceğiz. Bunun yanında bir de tip içerisinde AddCompleted isimli bir event bulunmakta. Şu anda elimizde var olan imkanlarla callback mekanizmasını kullanarak asenkron çağrımları gerçekleştirdiğimizi hatırlamamız gerekirse şu anda yapabileceğimiz implementasyon aşağıdaki gibi olacaktır.

private void btnTopla_Click(object sender, RoutedEventArgs e) {
 MathServiceClient client = new MathServiceClient();
 client.AddCompleted += (_sender, _e) =>
 {
 lblSonuc.Content = String.Format("Sonuc : {0}", _e.Result);
 };
 client.AddAsync(double.Parse(txtSayi1.Text), double.Parse(txtSayi2.Text));
}

Peki esas konumuza geri dönersek async&await kullanarak ve callback mekanizmasını kullanmadan asenkron çağrılarımızı nasıl  yönetebiliriz.

Bu noktada şu anda kullanabileceğimiz 2 yöntem bulunmakta.

Task.Factory.FromAsync

Yöntemlerimizden ilki Task tipi içerisinde bulunan FromAsync metodu. Bu metot Begin/End yapısı ile asenkronluğu sağlayan yani Asynchronous Programming Model Pattern’nına uygun olan işlemlerin Task tipi içerisine alınması ve Task tipi ile yönetilmesini sağlamakta. Böylece artık yapacağımız çağrımlarda ve diğer işlemlerde de bu metottan dönen Task tipini rahatça kullanabileceğiz.

İşte bu yöntemi kullanarak aslında proxy tipimiz içerisinde metotlara yeni bir metot ekleme imkanına sahibiz. Öyleyse hemen proxy tipimiz için bir extension metot gerçekleştirelim.

public static class Extensions
{
 public static Task<double> AddTaskAsync(
 this MathServiceClient client, double x, double y)
 {
 return Task.Factory.FromAsync<double>(client.BeginAdd(x, y, null, null), 
 client.EndAdd);         
 }
}

Şimdi web servis metodumuz async & await kullanabileceğimiz duruma geldi. Yani awaitable pattern'a uydu. Öyleyse async ve await kullanarak asenkron çağrımızı gerçekleştirelim.

private async void btnTopla_Click(object sender, RoutedEventArgs e)
{
 MathServiceClient client = new MathServiceClient();
 double result = await client.AddTaskAsync(double.Parse(txtSayi1.Text), 
 double.Parse(txtSayi2.Text));             
 lblSonuc.Content = String.Format("Sonuc : {0}", result);
}

Evet gördüğünüz gibi asenkron çağrımı gerçekleştirdik. Ancak uyguladığımız yönteme bakarsak bizim içim maliyetli bir yöntem olduğunu görmekteyiz. Bunun nedeni de her bir web servis metodu için yeni bir extension metot gerçekleştirmemiz. Bize aslında otomatik proxy kodu üretimi sırasında devreye girecek olan birşeyler lazım. Öyleyse gelin diğer yöntemimizi inceleyelim.

TaskWsdlImporterExtension

Visual Studio Async CTP kütüphanesini bilgisayarımıza kurduktan sonra Documents\Microsoft Visual Studio Async CTP\Samples klasörü içerisinde hem C# hem de VB ile yazılmış çeşitli asenkron programlama örnekleri gelmekte. Bu örneklerden biri de Stock Quotes isimli örnek. Bu örnek içerisinde bir istemci uygulaması tarafından WCF servis çağrılmakta ve bu servis üzerinden de hisse senedi kağıtlarının fiyatları alınmakta. Örnek içerisinde yapılan WCF servis çağrıları ise bizim örneklerimizde olduğu gibi asenkron çağrılar. Ancak burada client tarafında proxy codelarının üretilmesi sırasında bir extension kütüphanesi kullanılmış ve kullanılan extension kütüphanesi ise proje içerisine konulmuş. Bu kütüphane biz WCF servisini projemize servis referansı eklerken devreye girmekte ve proxy kodlarını async & await keywordleri ile beraber kullanılabilir biçimde yani awaitable pattern’a uygun olarak üretmekte. Şimdi bu örneği açıp derleyelim ve oluşan dll’i projemize referans olarak ekleyelim.

Şimdi bir önceki yöntemimizde projemize eklemiş olduğumuz WCF servis referansını kaldıralım ve sonrasında istemci uygulamamızın config dosyasına aşağıdaki config ayarları ekleyelim. Böylece projemize eklemiş olduğumuz extension, servis proxy kodunun üretilmesi sırasında devreye giriyor olacak.

<system.serviceModel>     
<client>       
<metadata>         
<wsdlImporters>           
<extension type="TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, 
 TaskWsdlImportExtension" />         
</wsdlImporters>       
</metadata>     
</client>   
</system.serviceModel>
Şimdi WCF servisimizin referansını yine yukarıda yaptığımız gibi asenkron çağrım metotlarının da üretilmesi seçeneğini seçerek yeniden ekleyelim. Referansı ekledikten sonra oluşturulan kodlara baktığımızda AddTaskAsync isimli bir servis metodu göremiyoruz. Ancak bakın  AddAsync isimli metodun dönüş tipi ne oldu :)

Task<double> tipi demek bizim için awaitable demek :) Yani async & await kullanımı demek. O zaman kodumuzu yeni metot ismine göre güncelleyelim ve uygulamamızı çalıştıralım.

private async void btnTopla_Click(object sender, RoutedEventArgs e)
{
 MathServiceClient client = new MathServiceClient();
 double result = await client.AddAsync(double.Parse(txtSayi1.Text), 
 double.Parse(txtSayi2.Text));             
 lblSonuc.Content = String.Format("Sonuc : {0}", result);
}

Uygulamayı çalıştırdığımızda gördüğünüz gibi uygulamamız istediğimiz gibi çalıştı. Toplama isteğini servise gönderdik ve gönderdikten sonra kontrol uygulamaya geri döndü ve sonrasında ilgili kod işletilerek yanıt ekrana yazdırıldı. Peki bu extension arka planda nasıl bir kod üretti :) Hemen metot üzerinden F12’ye basarak arka planda üretilen koda sıçrayalım :)

public System.Threading.Tasks.Task<double> AddAsync(double x, double y) 
{     return System.Threading.Tasks.Task<double>.Factory.FromAsync(
 new System.Func<double, double, System.AsyncCallback, 
 object, 
 System.IAsyncResult>(((IMathService)(this)).BeginAdd), 
 new System.Func<System.IAsyncResult, double>(((IMathService)(this)).EndAdd),
 x, y, null); }
Siz ilk yönteme göre bir fark görebildiniz mi ? Ben göremedim :) Sadece FromAsync metodunun bir başka overload hali kullanılmış :)

Asenkron web servis çağrımları bana göre biz developerlar için en büyük sıkıntıyı oluşturan durumların başında gelmekte. Sürekli olarak callback atamaları developerlar için yönetmesi ve ölçeklendirilmesi oldukça sıkıntılı durumlar doğurmakta. Bu nedenle C# 5.0 ile beraber gelecek olan Async&Await ile artık bu sorunları da geride bırakmış olacağız.

Makalemizi sonlandırmadan Web Service Referansı ekleme sırasında bu özelliğin C#’ın 5.0 ve dolayısıyla da Visual Studio’nun bir sonraki sürümüyle beraber built-in olarak geleceğini sizlere tekrardan hatırlatmak isterim. Bu makalede işlemiş olduklarımız sadece şu anda bizlere kolaylık sağlayabilecek 1-2 trick nokta. ;)

Yazımız boyunca yaptığımız örneği aşağıdaki linkten indirebilirsiniz.

AsyncWCFCalls

Bir sonraki makalemizde görüşmek üzere,

Hoşçakalın