İlkay İlknur
Just a developer...

Nedirtv Şubat Webinerleri (Level 300 Özel) ve C# 5.0 Webinerim

Cuma, 3 Şubat 2012 09:37 by ilkayilknur

Merhaba Arkadaşlar,

Çektiğim görsel dersler ile katkıda bulunduğum nedirtv'nin bu ay düzenlemiş olduğu Level 300 webinerlerinde 23 Şubat'ta "Tüm Yönleriyle C# 5.0 Asenkron Programlama" isimli bir webiner düzenliyor olacağım. Webinerlerin teması level 300 olsada webiner başında en basit örnekler ile başlayıp C# 5.0'ı temelden incelemeye başlayacağız ve seviyemizi yavaş yavaş arttıracağız. Bu nedenle webinere her seviyeden developerlar katılabilir ;)

Şubat Webinerlerinin Programı Şu Şekilde

Konu: Veritabanı Yerine Cache'deki Verilerle Çalışmak

Zaman: 6 Şubat 2012 Pazartesi 21:00

Konuşmacı: Uğur UMUTLUOĞLU

Link: https://www.livemeeting.com/cc/mvp/join?id=TF2CJN&role=attend

Konu: Dynamics CRM'e Plugin Yazma

Zaman: 13 Şubat 2012 Pazartesi 21:00

Konuşmacı: Barış KANLICA

Link: https://www.livemeeting.com/cc/mvp/join?id=T8DDJ7&role=attend

Konu: Entity Framework - Migration

Zaman: 20 Şubat 2012 Pazartesi 21:00

Konuşmacı: Burak Selim ŞENYURT

Link: https://www.livemeeting.com/cc/mvp/join?id=P4P5JG&role=attend

Konu: Tüm Yönleriyle C# 5.0 Asenkron Programlama

Zaman: 23 Şubat 2012 Perşembe 21:00

Konuşmacı: İlkay İLKNUR

Link: https://www.livemeeting.com/cc/mvp/join?id=7H5R69&role=attend

Konu: Masaüstü Uygulamasından ASP.NET Sayfalarıyla Etkileşim 

Zaman: 27 Şubat 2012 Pazartesi 21:00

Konuşmacı: Fatih BOY

Link: https://www.livemeeting.com/cc/mvp/join?id=68WBPH&role=attend

23 Şubat'ta Görüşmek Üzere,

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

C# 5.0 Async & Await Arka Planda Neler Oluyor ?

Çarşamba, 11 Ocak 2012 11:59 by ilkayilknur

Merhaba Arkadaşlar,

Sizlerle şu ana kadar C# 5.0 ile beraber gelecek olan Asenkron Programlama yeniliklerini hangi noktalarda kullanabileceğimizi inceledik. Ancak bildiğiniz gibi her programlama dili yeniliği aslında arka planda yeni bir sihir yaratır. Daha önce kodumuzu neredeyse tamamen değiştirerek asenkron hale getirirken artık sadece 1-2 ufak değişiklikle kodumuzu asenkron bir şekilde çalışacak hale getirebiliyoruz. Peki gerçekten arka planda neler oluyor ? Compiler neler yapıyor da biz sadece 1-2 kod değişikliği ile kodumuzu asenkron hale getirebiliyoruz.

İlk olarak aşağıdaki gibi bir asenkron metodumuz olduğunu düşünelim.

static async Task<string> DownloadStringAsync(string Url)
{
    WebClient client = new WebClient();
    return await client.DownloadStringTaskAsync(Url);
} 

Şimdi yazmış olduğumuz kodu derleyelim ve sonrasında Reflector yardımıyla açalım ve bakalım derleme sonucunda asenkron metodumuz compiler tarafından nasıl yeniden yazılmış

Dikkat : Yazının bu kısmından sonrası aşırı miktarda compiler tarafından üretilen kod içermektedir. Eğer compiler tarafından üretilen kodlara karşı alerjiniz varsa bu satırlar sizin için son çıkıştır. Ancak yazıyı buraya kadar okuduysanız sizinde aslında arka planda benim gibi neler olduğunu merak ediyor olduğunuzu düşünüyorum :)

 

 

 

private static Task<string> DownloadStringAsync(string Url)
{
        <DownloadStringAsync>d__0 d__;
        d__ = new <DownloadStringAsync>d__0(0) {
            Url = Url,
            <>t__MoveNextDelegate = new Action(d__.MoveNext),
            $builder = AsyncTaskMethodBuilder<string>.Create()
        };
        d__.MoveNext();
        return d__.$builder.Task;
}

Gördüğünüz gibi compiler, bizim yazmış olduğumuz DownloadStringAsync isimli metodu arka planda bir takım tipleri kullanarak yeniden yazdı.

Önceki yazılarımızdan da hatırlayacağımız üzere asenkron metotların dönüş tipi Task, Task<T> veya void olmak zorunda. Ancak bu dönüş tipini her zaman bizim yaratıp döndürmemiz gerekmemekte. Nitekim await kullanarak WebClient tipi içerisindeki DownloadStringTaskAsync metodunu çağırdığımızda bu metodun dönüş tipinin string olduğunu kolayca görebilmekteyiz. Ancak gördüğünüz gibi biz aslında asenkron metodumuzun dönüş tipini Task<string> olarak belirttik ve baktığımız zaman return ifadesinde de string döndürdük. Baktığımız zaman bu kullanım aslında tamamen legal. Ancak tabi ki bu işlem arka planda bir takım dönüşümler gerektirmekte. Bu dönüşümlerden ilki de Task<string> tipinin arka planda yaratılması ve metottan geri döndürülerek yazdığımız kodun arka planda da legalleştirilmesi. İşte compiler tarafından yazılan kodda görmüş olduğunuz AsyncTaskMethodBuilder tipi tam da bu işi gerçekleştirmekte.

public struct AsyncTaskMethodBuilder<TResult>
{
    public Task<TResult> Task { get; }
    public void SetResult(TResult result);
    public void SetException(Exception exception);
}

AsyncTaskMethodBuilder tipi gördüğünüz gibi içerisinde metot tarafından döndürülecek olan Task tipini ve metot işletilmesinden sonra da bu Task tipi içerisine sonucu yazmakta kullanılan SetResult isimli metodu içermekte. Bir de tabi eğer metot çalışması sırasında bir exception oluşursa bu exception’ı yine Task tipi içerisine yerleştirmek için bir de SetException metodunu bulundurmakta.

Compiler tarafından yeniden yazılan DownloadStringAsync isimli metodumuz içerisinde bir de <DownloadStringAsync>d__0 isimli bir tipin kullanıldığını görüyoruz. Bu tip te compiler tarafından yazılmış olan ve asenkron akışı tamamen içerisinde barındıran (enkapsüle eden) bir tip. Compiler tarafından yaratılmış olan bu tipin kodu ise şu şekilde.

[CompilerGenerated]
private sealed class <DownloadStringAsync>d__0
{
    // Fields
    private bool $__disposing;
    public AsyncTaskMethodBuilder<string> $builder;
    private int <>1__state;
    public string <>3__Url;
    public Action <>t__MoveNextDelegate;
    private TaskAwaiter<string> <a1>t__$await3;
    public WebClient <client>5__1;
    public string Url;

    // Methods
    [DebuggerHidden]
    public <DownloadStringAsync>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }

    [DebuggerHidden]
    public void Dispose()
    {
        this.$__disposing = true;
        this.MoveNext();
        this.<>1__state = -1;
    }

    public void MoveNext()
    {
        string <>t__result;
        try
        {
            string <1>t__$await2;
            bool $__doFinallyBodies = true;
            if (this.<>1__state != 1)
            {
                if (this.<>1__state != -1)
                {
                    this.<client>5__1 = new WebClient();
                    this.<a1>t__$await3 = this.<client>5__1.DownloadStringTaskAsync(this.Url).GetAwaiter<string>();
                    if (this.<a1>t__$await3.IsCompleted)
                    {
                        goto Label_0089;
                    }
                    this.<>1__state = 1;
                    $__doFinallyBodies = false;
                    this.<a1>t__$await3.OnCompleted(this.<>t__MoveNextDelegate);
                }
                return;
            }
            this.<>1__state = 0;
        Label_0089:
            <1>t__$await2 = this.<a1>t__$await3.GetResult();
            this.<a1>t__$await3 = new TaskAwaiter<string>();
            <>t__result = <1>t__$await2;
        }
        catch (Exception <>t__ex)
        {
            this.<>1__state = -1;
            this.$builder.SetException(<>t__ex);
            return;
        }
        this.<>1__state = -1;
        this.$builder.SetResult(<>t__result);
    }
}

Compiler tarafından yaratılan tipin constructor metoduna state isimli bir int değerin parametre olarak alındığını görüyoruz. Bu state değişkeni aslında bizim asenkron metot akışımızda önemli bir yere sahip. Bu yüzden <>1__state alanını aklımızın bir köşesinde tutalım ;)

Aslında sizin de göreceğiniz üzere <DownloadStringAsync>d__0 tipi içerisinde asenkron akışı yöneten metot MoveNext metodu. Bu metot içerisinde peki neler oluyor ?

Bu metodu incelemeden önce DownloadStringAsync isimli metodumuzun içerisine geri dönelim ve yapılan değer atamalarına bir bakalım.

private static Task<string> DownloadStringAsync(string Url)
{
     <DownloadStringAsync>d__0 d__;
     d__ = new <DownloadStringAsync>d__0(0) {
          Url = Url,
          <>t__MoveNextDelegate = new Action(d__.MoveNext),
          $builder = AsyncTaskMethodBuilder<string>.Create()
      };
      d__.MoveNext();
      return d__.$builder.Task;
}

  • İlk olarak constructor metoda 0 değeri parametre olarak geçilmiş. Bu değer de hatırlayacağımız üzere <DownloadStringAsync>d__0 tipi içerisindeki state değişkenine atanmakta.
  • <>t__MoveNextDelegate isimli delegate tipine de yine <DownloadStringAsync>d__0 tipi içerisindeki MoveNext metodu atanmakta.
  • Daha sonra ise yukarıda incelemiş olduğumuz AsyncTaskMethodBuilder tipinin Create isimli Factory metodu çağrılarak ilgili tipten bir nesne örneği $builder değişkenine atanmakta.
  • Son olarak ise nesne örneği yarattıktan sonra bu örnek üzerinden MoveNext metodu çağrılmakta.

Evet şimdi MoveNext metoduna geri dönüyoruz.

MoveNext metodu içerisinde ilk çağrım sırasında state değişkenimizin değeri 0. Bu nedenle metot içerisindeki 2 if ifadesinden de geçerek aşağıdaki kod bloğu işletilmekte.

if (this.<>1__state != 1)
{
   if (this.<>1__state != -1)
   {
        this.<client>5__1 = new WebClient(); 
        this.<a1>t__$await3 = this.<client>5__1.DownloadStringTaskAsync(this.Url).GetAwaiter<string>(); 
        if (this.<a1>t__$await3.IsCompleted)
        {
             goto Label_0089;
        } 
        this.<>1__state = 1;
        $__doFinallyBodies = false;
        this.<a1>t__$await3.OnCompleted(this.<>t__MoveNextDelegate); 
   } 
   return;

}

Çalışan koda baktığımızda bizim orjinal kodumuz içerisinde yazmış olduğumuz kodların bu kısımda olduğunu görmekteyiz. İlk olarak WebClient tipinden bir nesne örneği yaratılmakta ve akabinde de bu tip üzerinden DownloadStringTaskAsync isimli metot çağrılmakta.

Önceki yazılarımızda hatırlarsanız async & await kullanmamız için “awaitable pattern”’ı uygulamamız gerektiğinden sıkça bahsetmiştik. İşte awaitable pattern tam da kodumuzun bu noktasında devreye girmekte. Bir işlemin awaitable olması için aslında içerisinde GetAwaiter metodu ile aşağıdaki tipi döndürmesi gerekmekte.

public struct TaskAwaiter<TResult>
{
    public bool IsCompleted { get; }
    public void OnCompleted(Action continuation);
    public TResult GetResult();
}

Bu tip aslında compiler tarafında oldukça önem arz etmekte. Bunun nedeni ise compiler arka planda yani yukarıdaki kodlarda bu tip üzerinden asenkron işlemin tamamlanıp tamamlanmadığını kontrol etmekte. Eğer işlem tamamlanmadıysa OnCompleted metodunu kullanarak bir callback ataması yapmakta. Tabi birde işlemin sonucunu elde edebilmek için GetResult metodunu çağırmakta.

TaskAwaiter tipi ile ilgili açıklamamızdan sonra tekrar MoveNext isimli metoda geri dönersek artık sanırım neden GetAwaiter metodunun çağrıldığını daha fazla açıklamamıza gerek yok. :)

GetAwaiter metodunun çağrılması sonrasında elde edilen TaskAwaiter tipi üzerinden ise bir takım kontroller yapılmakta. Bu kontrollerden ilki yapılan asenkron çağrımın sonlanıp sonlanmadığı ile ilgili. Eğer işlem sonlandıysa kod akışı doğrudan Label_0089 isimli alana goto ifadesi ile yönlenmekte. Eğer sonlamadıysa öncelikle state değişkeni 1’e çekilmekte daha sonra ise TaskAwaiter tipi içerisindeki OnCompleted metodu çağrılmakta ve callback olarak ta <DownloadStringAsync>d__0 tipi içerisindeki <>t__MoveNextDelegate alanı verilmekte. Yazımızın başlarında hatırlarsanız bu alana da MoveNext metodu atanmıştı !!! . Bu da demek oluyor ki asenkron işlem tamamlandıktan sonra yine MoveNext metodu çağırılıyor olacak. Sonrasında ise return ifadesi işletilerek MoveNext metodu sonlanmakta ve kontrol çağıran koda geri dönmekte yani bizim kodumuza. Bu şekilde de aslında bizim kullanıcı arayüzümüz cevap verebilir durumda olmakta.

Şimdi asenkron çağrım sonrasında işlemin sonlandığını ve TaskAwaiter tarafından OnCompleted metoduna parametre olarak geçirilen MoveNext metodunu işlemin sonlanmış olduğunu düşünerek tekrar işletelim. Bir üstteki paragrafta state değişkenine 1 değeri atanmıştı. Bu nedenle MoveNext metodu içerisindeki if kontrolleri girmeden doğrudan devam edecek ve ilk olarak state değişkenine 0 değeri atanacak ve sonrasında da Label_0089  ismiyle etiketlenmiş kod bloğu çalışacak. Bu kod bloğu içerisinde de TaskAwaiter tipi içerisindeki GetResult isimli metot kullanılarak asenkron metodun sonucu alınmakta.  Son olarak ise catch ifadesinin altında bulunan kısım işletilmekte ve state değişkenine –1 değeri atanmakta ve son olarakta AsyncTaskMethodBuilder tipi içerisindeki SetResult metodu kullanarak işlem sonucu AsyncTaskMethodBuilder tipi içerisindeki Task’ın içerisine yazılmakta.

MoveNext metoduna dikkat ettiyseniz bir de try-catch bloğu bulunmakta. Bu blok içerisinde de asenkron işlem sırasında oluşabilecek olan Exceptionlar yakalanmakta ve bir exception yakalanması durumunda state değişkeni –1’e çekilmekte ve oluşan exception da AsyncTaskMethodBuilder tipi içerisindeki SetException metodu çağrılarak AsyncTaskMethodBuilder tipinde bulunan Task’ın içerisindeki Exception alanına yazılmakta.

Genel yapıyı incelediğimizde aslında aklımıza C# dili içerisinde uzun süreden beri bulunan yield keywordünün alt yapısı gelmekte. Yield keywordü hatırlayacağınız üzere arka planda bir state machine yaratmaktaydı. C# 5.0 ile beraber gelen asenkron programlama yeniliklerinde de compiler aslında yine benzer bir yapıyı kullanarak bir state machine yaratmakta ve bu state machine içerisinde de callback atamalarını gerçekleştirmekte. Peki compiler neden bir state machine yaratmakta ? Aslında bunun durumu oldukça basit. Asenkron işlemin yönetimini kolay yapabilmek ve mevcutta bulunulan durumları saklayabilmek.

Özet olarak C# 5.0 ile gelen asenkron programlama yeniliklerinin arka planında callback tabanlı bir state machine yapısı yatmakta diyebiliriz.

Şu ana kadar Task tipinden bir nesne örneği döndüren asenkron metodun arka planını inceledik. Peki void dönüş tipine sahip olan asenkron metotlar nasıl yeniden yazılmakta ? Aslında çokta fazla bir değişiklik bulunmamakta. Bu yüzden de uzun uzadıya bir örnek yapıp incelememize gerek yok diye düşünüyorum. Aradaki tek fark AsyncTaskMethodBuilder<T> tipi yerine adından da anlayabileceğiniz üzere AsyncVoidMethodBuilder tipinin kullanımı olmakta. Compiler dönüş değeri void olduğu için herhangi bir Task tipi üretme işine girmemekte. :)

Compilerların arka olanda gerçekleştirdikleri işlemleri bilmek bazılarımıza gereksiz geliyor olsa da aslında bence oldukça önemli. Çünkü bir takım kullanımlar aslında baktığımızda bize legal olarak gözükmese de compiler arka planda bu programlama diline has özellikleri legal bir şekle sokmakta ve bu detayları da biliyor olmamız aslında bizim en doğru kullanımları yapmamızı sağlamakta. ;)

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

Hoşçakalın,

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

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

C# 5.0 & VB.NET 11.0 Asenkron Programlama 5N1K (Ne,nerede,ne zaman,nasıl,neden,kim)

Perşembe, 22 Eylül 2011 15:56 by ilkayilknur

Merhaba Arkadaşlar,

“İşte geldik yine bir C# & VB.NET V.Next yazımıza daha :) Sanki daha dün C# 4.0 ile beraber gelen dynamic özelliğini incelliyor gibiydik. Teknoloji durmadan gelişiyor. Neyse konumuza dönelim“

BUILD konferansıyla beraber  Windows 8 ile artık kullanıcı deneyimlerinin oldukça değiştiğini görmekteyiz. Eskiden kullandığımız kullanıcı arayüzleri ile şu anda kullandığımız kullanıcı arayüzleri aslında oldukça fark var :) Tabi bunda artık sayısı oldukça artan dokunmatik cihazların da payı bulunmakta. Değişen kullanıcı arayüzleriyle beraber geliştirilen uygulamaların yapısı ve davranışları da değişim göstermekte.

Eskiden yazdığımız uygulamalarda network yoğun veya I/O yoğun işlemleri gerçekleştirirken uygulamamızın arayüzü bir süreliğine kilitlenirken artık günümüzde kullandığımız altyapıların da nimetlerini kullanarak bu işlemleri genelde ayrı bir thread’e alarak bu yoğun işlemi UI thread’ten alıyoruz ve arayüzün kullanıcının hareketlerine  sürekli olarak cevap verebilmesini sağlayabiliyoruz. Yani işlemleri Asenkron olarak gerçekleştiriyoruz.

Kullanıcının deneyimini bu şekilde iyileştirirken şimdi bir de developer perspektifinden durumu inceleyelim :)

Developer Perspektifinden Asenkron Programlama

Şimdi gelin uygulama tarafında senkron ve asenkron kavramlarına WebClient tipi içerisinde bulunan DownloadString metodu ile göz atalım.

Senkron İşlem : WebClient tipi içerisinde bulunan DownloadString(…) metodunu çağırdığımızda senkron bir işlem gerçekleştirmekteyiz. DownloadString metodunun çalışma süresi boyunca uygulama metodun işlemini bitirmesi için beklemekte ve işlem bittikten sonra uygulama akışı devam etmektedir.

Asenkron İşlem : WebClient tipi içerisinde bulunan  DownloadStringAsync(…,Action<string> callback) metodu ise asenkron olarak çalışmakta. Metot çağırıldığı anda işlem başlatılmakta ve kontrol hemen metot çağırımı yapan kısma geri dönmekte ve böylece uygulama kaldığı yerden işlemlere devam edebilmektedir. Burada aklımıza gelen soru ise şu olmakta: “Peki işlemin tamamlandığını nasıl anlayabiliyoruz ? ” Bunun için de DownloadStringAsync metodu içerisine bir callback’i parametre olarak geçiriyoruz ve asenkron işlem tamamladığında uygulama doğrudan bu callback metoduna düşüyor ve bizlerde işlemin bittiğinden haberdar olabiliyoruz.

Eğer daha önce bu şekilde asenkron geliştirmelerde bulunduysanız callback kullanarak uygulama geliştirmek bazen acı çektirebiliyor bizlere. Lambda ifadeleriyle callback tanımlamak biraz daha kolay hale gelse de yaptığımız ayrı ayrı tanımlamalarla kodumuzu okunması ve anlaşılması zor bir hale getiriyoruz.

Yani Senkron programlama yaparken sahip olduğumuz rahatlığa asenkron programlama yaparken sahip olamıyor(duk)uz.

C# 5.0 & VB.NET 11.00 Ana Tema => Asenkron Programlama

 

C#VBEvolution

Veee evet C# ve VB’nin gelecek versiyonlarının teması Asenkron programlama olarak belirlendi. Geçtiğimiz sene ilk olarak PDC’de duyurulan Asenkron programlama yapısı bu sene BUILD konferansında yapılan C# 5.0 ve VB.NET 11.0 duyurularıyla beraber artık adından daha da fazla söz ettirir oldu. :)

Yeni versiyonlarla beraber hayatımıza async ve await adında 2 adet yeni keyword giriyor olacak. Bu keywordler ile beraber asenkron işlemlerimizin orkestrasyonunu tıpkı senkron bir şekilde kod yazar gibi sağlayabiliyor olacağız. Geri kalan tüm işleri ise artık compiler tarafına bırakıyor olacağız. Compiler gerekli durumlarda yaptığımız bildirimlere göre arka planda kodumuzu değiştirerek gerekli eklemeleri yapıyor olacak.

Async & Await  Asenkron İşlemler Yapabilmek İçin Değil !!! Asenkron İşlemlerin Orkestrasyonu İçin Kullanılır.

 

Doğabilecek kafa karışıklıklarının önüne geçmek için yukarıdaki cümleyi iyi bir şekilde okumamızda fayda var. Çünkü metodunuzun başına async keywordünü koyarak o metodu asenkron yapamayacaksınız. :) Zaten elimizde bir işlemi asenkron bir şekilde çalıştırmak için .NET Framework 4.0 ile gelen bir Task tipi bulunmakta diyerek hiç kod paylaşımı yapmayacağım bu yazıda ufak tefek ipuçlarını da sizlerle paylaşıyorum :) .

NET Framework 4.0 ile beraber gelen en büyük yeniliklerden birinin de parallel programlama yenilikleri olduğunu düşünürsek compiler tarafında yapılan bu yeniliklerle özellikle artık uygulama geliştirme tarafında concurrent programlama kavramlarının daha da popüler olacağını söylemek herhalde yanlış olmayacaktır. Böylece hem birim zamanda daha fazla iş yaparak CPU’ları daha etkin bir şekilde kullanırken hem de uygulama arayüzlerinin cevap verebilir(responsive) olmasını sağlayabilir duruma geliyor olacağız.

Yazımı Build konferansında Anders Hejlsberg’in yaptığı sunumda kullandığı bir cümle ile bitiriyorum. :)

Async

Bu kısa yazımızda C# ve VB’nin bir sonraki sürümüyle beraber gelecek ana yenilikleri hiç kod kısmına girmeden tamamen high-level olarak bu yazıda sizlere sunmayı hedefledim.

Umarım faydalı bir yazı olmuştur .

Sonraki yazılarımızda artık kod tarafına girerek Async özelliklerini incelemeye başlıyor olacağız. ;)

Hoşçakalın,

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