İlkay İlknur
Just a developer...

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

Çarşamba, 4 Ocak 2012 22:17 by ilkayilknur

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.

GenerateAsync

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.

TaskwsdlImporter

Ş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 :)

AddAsync

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.zip (167,48 kb)

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

Hoşçakalın

Tags:   , , ,
Categories:   Async | C# | C# 5.0 | WCF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (1) | Yorumlar RSSRSS Yorum Takibi
Share

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

Perşembe, 29 Aralık 2011 10:29 by ilkayilknur

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.

Tags:   , ,
Categories:   .NET Framework 4.0 | .NET Framework 4.5 | Async | C# | C# 5.0
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Yorumlar RSSRSS Yorum Takibi
Share

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

Perşembe, 1 Aralık 2011 23:46 by ilkayilknur

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>

 

AsyncBaseWindow

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.

SyncvsAsyncClassic

SyncvsAsync

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.

Await

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.

Not 2 : Yukarıda yazılan tüm kodlar VB için de geçerlidir. Kodların VB hali en kısa zamanda yazıya eklenecektir.

Yazımız boyunca yazdığımız C# kodlarını aşağıdan indirebilirsiniz.

AsyncStuff.rar (247,53 kb)

 

Tags:   ,
Categories:   C# | C# 5.0 | Async
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Yorumlar RSSRSS Yorum Takibi
Share