İlkay İlknur
Just a developer...

The Roslyn Project - 5N1K (Ne,nerede,ne zaman,nasıl,neden,kim)

Çarşamba, 25 Ocak 2012 13:34 by ilkayilknur

Merhaba Arkadaşlar,

C# 4.0 ve VB.NET 10 programlama dili sürümleriyle beraber Microsoft tarafında bu iki programlama dilini geliştiren ekipler tek bir çatı altında toplandı. Tek bir çatı altında toplanmasının temel nedeni de aslında programlama dillerini kullanan developerların benzer ihtiyaçlarının olması ve programlama dillerinin özelliklerinin birbirlerinden farklı olmasının önüne geçilerek  ortak temalar üzerinden ilerlenmesi idi.

Tüm bu çalışmalar kapsamında C# 4.0 sürümünün release olmasından sonra programlama dili ekipleri 2 ayrı takıma ayrılarak geliştirmelere devam etmekteler. Bir ekip şu anda C# 5.0 ve VB.NET 11 ( programlama dilleri arasındaki farklılıkları gidermek amacıyla VB 11 ile beraber C#’ta uzun zamandır bulunmakta olan Iterators özelliği geliyor olacak. ) dediğimiz programlama dillerinin bir sonraki versiyonu olan ve ana teması Asenkron Programlama olarak belirlenen geliştirmelere devam ederken bir diğer ekipte daha uzun soluklu bir proje olan Roslyn kod adlı projeye devam etmekteler.

Özellikler C# programlama dilinin geleceği ile ilgili araştırmalar yaptıysanız Compiler as a Service(CaaS) konseptiyle mutlaka karşılaşmışsınızdır. İşte Roslyn projesinin temel amacı da bu. Compiler as service, yani compiler’ın bir API vasıtasıyla servis olarak dışarıya açılması.

Şu anda C# ve VB compilerının yapısına  baktığımız zaman compilerların tam bir kara kutu olduğunu söyleyebiliriz. Çünkü compilerlar yazmış olduğumuz kaynak kodu input olarak alırlar , içeride sihir gerçekleşir ve output olarak .NET assemblyleri üretirler. Compiler aslında bu sihrin gerçekleşmesi sırasında yazmış olduğumuz kod ile ilgili pek çok analiz yapar ve bir takım yapılar oluşturur. Ancak .NET assemblysi üretilmesiyle beraber compiler yaratmış olduğu bu yapıları tamamen siler ve unutur.

Ayrıca syntax highlighting, go to definition, formatting veya refactoring gibi üretkenliğimizi arttıracak olan özelliklerde de baktığımız zaman IDE tarafından sağlanan bu özelliklere sıkı sıkıya bağlı olduğumuz görmekteyiz. Developer olarak kendi ihtiyaçlarımızı karşılayacak olan refactoring mekanizmalarını gerçekleştirmemiz oldukça zor. Aslında syntax highlighting, go to definition, formatting veya refactoring gibi özellikleri incelediğimizde tüm bu özellikler arka planda compiler’ın kodu derleme ve Assembly üretme sırasında kullanmış ve yaratmış olduğu yapıları kullanmakta.

Tüm okuduklarımızı düşündüğümüze Roslyn projesi ile amaçlanan compilerın kara kutu olmaktan çıkarılarak ve içerisinde bulunan bilgilerin bir API vasıtasıyla developerlara sunulmasıdır. Bu doğrultuda da C# ve VB compilerları kendi dillerinde !!! yani managed programlama dilleriyle yeniden implemente edilemekte. Evet yanlış okunmadınız :) Bir takım tarihsel nedenlerden dolayı C# ve VB compilerları C++ programlama dili ile yazılmıştır. Ancak Roslyn projesinden C# ve VB compilerları kendi dillerinde yeniden yazılmaktalar.

Compiler içerisindeki yapıları bir API ile dışarı sunulması peki developerlar için neler sağlayacak ? Aslında yapılabilecek pek çok şey bulunmakta. İlk aklımıza gelen, özellikle dinamik programlama dillerinde gördüğümüz Read-Eval-Print Loop’lar artık implemente edilebilir durumda olacak. Programlama dili içerisindeki scripting yeteneğinin bu şekilde hayata geçtiğini görüyor olacağız. Bunun yanında Meta-Programming dediğimiz aslında program yazan program olarak Türkçe’ye çevirebileceğimiz mekanizmalarda artık gerçekleştirilebiliyor olacak. Ayrıca Language-Object-Model’a sahip olarak, yazılan kod üzerinde tüm analizleri yapabiliyor olacağız. Kendi yazacağımız extensionlar veya uygulamalar ile çok çeşitli kendimize özel refactoring mekanizmalarını hayata geçirebiliyor olacağız. Ayrıca DSL(Domain – Spesific –Languages) Embeding dediğimiz kendi uygulamamıza has bir takım komutlar arasına C# veya VB kodu yerleştirebiliyor ve bu kodları da istediğimiz zaman işletebilir durumda oluyor olacağız.

Roslyn APIs

Yazmış olduğumuz kodların .NET assembly’si haline çevrilmesi süreci aslında compiler içerisinde 4 farklı süreci içerisinde barındırmakta.

Bunlar,

  • Parser : Kaynak kodu geçmiş olduğu ilk süreçtir. Bu süreç içerisinde kaynak kod parse edilerek kod tokenlara ayrılır. Ayrılan tokenlar aynı zamanda yapısal bir veri yapısına getirilirler. (Syntax Tree)
  • Symbols & Metadata Import : Bu aşamada kaynak kod içerisindeki tanımlamalar bulunur ve analiz edilir. Daha sonrasında ise sembol tabloları oluşturulması amacıyla bu tanımlamalar ile ilgili metadatalar okunarak sembol tabloları oluşturulur.
  • Binder : Bu aşamada da sembol tablosundaki tanımlamalarla kod içerisinde yapılmış olan tanımlamalar eşleştirilir.
  • IL Emitter : Son aşama olan bu aşamada ise tüm oluşturulan yapılar ve bilgiler birleştirilerek .NET Assembly’si oluşturulur.

Peki Compiler API’ları tam olarak bu süreçlerin neresine oturmakta ?

Compiler API’larını incelediğimizde aslında her bir süreci ayrı olarak içerisinde barındıran farklı API’ları görmekteyiz. Parsing süreci içerisinde Syntax Tree API bulunurken bu API ile Parsing işlemi sürecinde üretilen Syntax Tree üzerinde işlemler gerçekleştirilebilmekte. Aynı şekilde Symbol API, Binding and Flow APIs ve Emit API da geri kalan süreçler ile ilgili işlemleri içerlerinde barındırmakta.

Önceki paragraflarda IDE içerisinde bulunan ve developerların özellikle üretkenliklerini arttıran özelliklerin aslında compiler tarafından üretilen yapıları kullandığından bahsetmiştik. Peki hangi özellikler hangi aşamadaki bilgileri ve API’ları kullanmakta ?

Formatting, colorizer ve outlining gibi özelliklerin Parsing işlemi sonrasında üretilen Syntax Tree’yi kullandığını görmekteyiz. Navigate To ve Object Browser özellikleri de Symbol API tarafından sunulan Sembol Tablosuna dayanmakta. Intellisense, Rename, Extract Method, Go To Definition gibi IDE içerisinde bulunan pek çok özellikte Binding işlemi sonucunda üretilen yapılara dayanmakta. Edit and Continue ise Emit API tarafından gerçekleştirilmekte.

Compiler as Service konsepti aslında oldukça geniş ve içerisinde pek çok bilgiyi barındıran bir konsept. Roslyn projesinin tamamlanmasıyla beraber pek çok özelliğin geleceğini ve çok yaratıcı uygulamaların ortaya çıkacağını söylemek sanırım yanlış olmaz. Bu uzun soluklu projeyi en başından itibaren takip etmenizi şiddetle öneriyorum.

Roslyn projesinin ne zaman tamamlanacağını soracak olursanız açıkcası bunun için süre vermenin çok doğru olacağını düşünmüyorum. Ancak şunu söyleyebilirim ki C# 5.0 ile gelmiyor olacak :)

Roslyn projesinin şu anda CTP sürümü bulunmakta. Bu sürümü buradaki adresten indirip kurabileceğiniz gibi aynı zamanda NuGet Package Manager’a Roslyn yazarak ta projenize ekleyebilirsiniz ;) .

Bir sonraki yazımızda Roslyn ile derinlere dalıyoruz ;)

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

Object,Collection ve Dictionary Initializers

Cumartesi, 21 Ocak 2012 15:34 by ilkayilknur

Merhaba Arkadaşlar,

Bu yazımızda sizlerle C# 3.0 sürümünden itibaren bizlerle olan ve C# tarafında object’lerin ve collection’ların yaratılması sırasında developerlara oldukça kolaylık sağlayan Object & Collection & Dictionary Initiliazers konusunu inceliyor olacağız.

İlk olarak Object Initializers tarafına bakarsak Object Initializer yapısı basit olarak biz developerların özellikle object yaratımı sırasında objectlerin propertylerine ilk değerleri atamamız konusunda syntax bakımından oldukça kolaylık sağlamakta. Bunun yanında Object Initiliazers özellikle LINQ sorgularında ve anonymous type yaratımında da hayati önem arzetmekte.

Şimdi öncelikle basit bir sınıf tanımlayalım ve daha sonra Object Initializer mekanizmasıyla yaratmış olduğumuz object’in propertylerini ilk değerlerine set edelim.

class MyClass 
{         
     public int Property1 { get; set; }
     public string Property2 { get; set; }
     public MySecondClass Property3 { get; set; }
}
class MySecondClass
{
     public int MyProperty { get; set; }
}

 

Şimdi object initializer kullanarak MyClass tipinden bir nesne örneği yaratalım.

MyClass myClass = new MyClass()
{
     Property1 = 1,
     Property2 = "Property2",
     Property3 = new MySecondClass()
     {
          MyProperty = 3
     }
};

 

Gördüğünüz gibi oldukça basit bir şekilde nesnemizi yarattık. Peki arka planda neler oluyor ? Öyleyse hemen arka plana geçelim ve bakalım neler oluyor.

Farklılıklar aslında yaratılan IL tarafında gerçekleşmekte.

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] class ConsoleApplication2.MyClass myClass,
        [1] class ConsoleApplication2.MyClass <>g__initLocal0,
        [2] class ConsoleApplication2.MySecondClass <>g__initLocal1)
    L_0000: nop
    L_0001: newobj instance void ConsoleApplication2.MyClass::.ctor()
    L_0006: stloc.1
    L_0007: ldloc.1
    L_0008: ldc.i4.1
    L_0009: callvirt instance void ConsoleApplication2.MyClass::set_Property1(int32)
    L_000e: nop
    L_000f: ldloc.1
    L_0010: ldstr "Property2"
    L_0015: callvirt instance void ConsoleApplication2.MyClass::set_Property2(string)
    L_001a: nop
    L_001b: ldloc.1
    L_001c: newobj instance void ConsoleApplication2.MySecondClass::.ctor()
    L_0021: stloc.2
    L_0022: ldloc.2
    L_0023: ldc.i4.3
    L_0024: callvirt instance void ConsoleApplication2.MySecondClass::set_MyProperty(int32)
    L_0029: nop
    L_002a: ldloc.2
    L_002b: callvirt instance void ConsoleApplication2.MyClass::set_Property3(class ConsoleApplication2.MySecondClass)
    L_0030: nop
    L_0031: ldloc.1
    L_0032: stloc.0
    L_0033: ret
}

Akışa baktığımızda aslında bizim yaptığımız işlemleri compiler arka planda açarak, teker teker tüm işlemleri gerçekleştirmekte ve yaklaşık olarak aşağıdaki kod örneğine çevirmekte.

MyClass myClass = new MyClass();
myClass.Property1 = 1;
myClass.Property2 = "Property2";
MySecondClass second = new MySecondClass();
second.MyProperty = 3;
myClass.Property3 = second;

 

Şimdi yukarıdaki kod ile bir önceki object initializer kullanarak yazdığımız kodu karşılaştırdığımızda özellikle okunulabilirlik bakımından oldukça büyük farklar bulunmakta. İlk kullanımda kodu okuyan developer hemen hızlı bir şekilde set edilen değerleri okuyup geçerken diğer örnekte ise tek tek referansları kontrol etmek zorunda kalmakta ve daha çok zaman kaybetmekte. Bunun yanında kodu yazan developer da ikinci kullanımda birinci kullanıma göre daha çok zaman kaybetmekte.

İşte bu noktadaki problemi çözmek için compiler devreye girmekte ve developerlara oldukça kolay bir tanımlama syntax’ı sağlarken arka planda da bu syntax’ın kullanıldığı kısımları yeniden yazarak aslında arka planda yine klasik kullanımı muhafaza etmekte ;)

Collection Initializers

C# 3.0 ile beraber gelen bir diğer syntax kolaylığı ise Collection Initializers’dı. Collection Initializers ile yarattığımız koleksiyonlar içerisine elemanları hızlı bir şekilde yerleştirebilmekteyiz.

Örneğin,

List<int> list = new List<int>()
{
      1,2,3,4,5,6,7
};

 

yukarıdaki gibi bir kullanımla bir List<T> tipinden biri koleksiyon örneği yarattıktan sonra içerisine 1’den 7’ye kadar elemanları ekleyebilmekteyiz. Bu kullanım aslında C# 3.0 öncesinde array’ler için geçerliydi. Ancak C# 3.0 ile beraber bu kullanım kolaylığı collection tipleri için de getirildi.

Peki collection initializers kullandığımızda arka planda neler oluyor ? :)

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1<int32> list,
        [1] class [mscorlib]System.Collections.Generic.List`1<int32> <>g__initLocal0)
    L_0000: nop
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
    L_0006: stloc.1
    L_0007: ldloc.1
    L_0008: ldc.i4.1
    L_0009: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_000e: nop
    L_000f: ldloc.1
    L_0010: ldc.i4.2
    L_0011: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_0016: nop
    L_0017: ldloc.1
    L_0018: ldc.i4.3
    L_0019: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_001e: nop
    L_001f: ldloc.1
    L_0020: ldc.i4.4
    L_0021: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_0026: nop
    L_0027: ldloc.1
    L_0028: ldc.i4.5
    L_0029: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_002e: nop
    L_002f: ldloc.1
    L_0030: ldc.i4.6
    L_0031: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_0036: nop
    L_0037: ldloc.1
    L_0038: ldc.i4.7
    L_0039: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
    L_003e: nop
    L_003f: ldloc.1
    L_0040: stloc.0
    L_0041: ret
}

Burada da baktığımızda aslında yazdığımız kodun aksine compiler arka planda öncelikle bir List nesne örneği yaratmakta ve sonrasında da tüm elemanları List tipinin Add metodunu kullanarak eklemekte. Aslında bizim normalde de kullanmamız gereken bu metodu compiler arka planda kendisi kullanmış ve biz developerlara daha kısa bir syntax ile uygulama geliştirme imkanı sağlamış.

Collection Initializer mekanizmasını kullanmamız için sadece tek bir kısıt bulunmakta. O da tipimizin IEnumerable interface’ini implemente etmesi ve tip içerisinde Add metodunun bulunması. Burada aklımıza gelen bir soru bulunmakta. Bu da arka planda generate edilen IL kodlarında IEnumerable interface’inin kullanılmamasına rağmen collection initializers mekanizmasında neden zorunlu olarak IEnumerable interface’inin tip tarafından implemente edilmek zorunda olduğu. Bunun nedeni de aslında C# programlama dili tasarım ekibi tarafından alınmış bir karara dayanmakta. C# 3.0 ile ilgili araştırmalar yaparken collection Initializers yapısının destekleneceği noktalar ekip tarafından incelenmiş ve bu özellik collection tipleri ile sınırlı bırakılmış ve bu nedenle de tipin IEnumerable interface’ini implemente etmesi gerektiği zorunlu hale getirilmiştir.

Dictionary Initializers

Bu yazımızda son olarak bahsedeceğimiz konu da Dictionary Initializers. Collection Initializers ile collection tipleri içerise kolay bir şekilde eleman ekleyebilirken, aslında bu ihtiyaca benzer bir durum da dictionary tipleri için doğmakta. Bazı durumlarda yarattığımız Dictionary’ler içerisine elemanları teker teker eklememiz gerekebilmekte. Bu noktada da yine Collection Initializers’da olduğu gibi C# tarafından sağlanan kolay bir syntax ile dictionary içerisine ilk elemanları atabilmekteyiz.

Dictionary Initializers’ın kullanımı ise şu şekilde olmakta.

Dictionary<int, string> a = new Dictionary<int, string>()
{
      {1,"Bir"},
      {2,"İki"},
      {3,"Üç"}
};

Peki compiler arka planda ne yapmakta ?

Dictionary<int, string> <>g__initLocal0 = new Dictionary<int, string>();
<>g__initLocal0.Add(1, "Bir");
<>g__initLocal0.Add(2, "İki");
<>g__initLocal0.Add(3, "Uc");
Dictionary<int, string> a = <>g__initLocal0;

Compiler kimi zaman bu yazımızda da gördüğümüz gibi bir takım kolay syntax yapılarını developerlara sunarken arka planda aslında bu syntax yapısını klasik kullanımlara geri çevirerek developerların yazılım geliştirme sürelerini kısaltmayı amaçlamakta. Kimi zaman da keywordler veya modifierlar sunarak arka planda kurduğu generic yapılarla çok daha farklı işleri biz developerlara hissettirmeden gerçekleştirmekte. ;)

Hoşçakalın

Tags:   , ,
Categories:   C# | C# 3.0 | Arka Planda Neler Oluyor ?
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

Interceptors ve Castle Windsor İle Gerçekleştirimi

Çarşamba, 21 Aralık 2011 22:16 by ilkayilknur

Merhaba Arkadaşlar,

Bu yazımızda bizim de kurumsal projelerde uzun süredir kullandığımız ayrıca diğer projelerde de sıklıkla kullanıldığını gözlemlediğim Interceptor konusunu inceliyor olacağız.

Yazımızın başında öncelikle interceptor konusuna giriş yapmadan önce sanırım Aspect Oriented Programming konusuna kısaca değinmemizde fayda bulunmakta. Bu şekilde Interceptor’ların tam olarak ne işe yaradığını, hangi konularda bizlere yardımcı olacağını daha iyi anlayabiliriz.

Aspect Oriented Programming

Aspect Oriented yaklaşımını incelediğimizde aslında temel olarak birbiri ile çakışan ilgilerin(cross-cutting) ayrılmasını hedeflediğini görmekteyiz. Peki bu ilgiler neler olabilir ?

Örneğin, geliştirdiğimiz yazılımlara baktığımızda aslında gerçekleştirdiğimiz business işlemleri sırasında bir takım işlemleri sürekli olarak gerçekleştirdiğimizi görmekteyiz. Hep beraber itiraf etmemiz gerekirse gerçekleştirmiş olduğumuz business metotlarının hemen hemen aşağıdaki gibi akışları bulunmakta. :)

static void OylesineBirBusinessMetot()
{
    try
    {
         MetodaGelenIlkIstegiLogla();
         IstegiYapanKullanicininYetkisiVarmi();
         //Business İslemlerini Gerçeklestir
         IslemSonucunuLogla();
    }
    catch (Exception ex)
    {
         HatayiLogla(ex);
    }
}

 

Yukarıda görmüş olduğumuz akışlar ve bu akışlara ek olarak metotlar içerisinde yapabileceğimiz bir takım diğer ortak işlemler aşağıdakiler olabilmekte.

  • Loglama İşlemleri
  • Validasyon İşlemleri
  • Authentication İşlemleri
  • ExceptionHandling İşlemleri
  • Caching İşlemleri

Yukarıdaki işlemlere baktığımızda aslında business katmanında yaptığımız işlemlerin büyük bir kısmını oluşturduğunu görmekteyiz. Hatta bu işlemleri çıkarttığımızda business metotlarımızın oldukça kısaldığını ve yalınlaştığını görebiliriz.

İşte aspect oriented programming yaklaşımı da yukarıda gördüğümüz gibi birbirlerinden farklı olan ancak çoğu kez kesişen ilgilerin birbirlerinden ayrılmasını amaçlamakta. Böylece geliştirilen yazılımların bakımlarının kolaylaştırılması, ölçeklendirilebilmesi amaçlanmakta.

Peki yukarıda gördüğümüz birbirleri ile çakışan ilgileri birbirlerinden nasıl ayıracağız ? İşte bu noktada karşımıza interceptor yapıları çıkmakta. Interceptor’lar belirli noktalarda metot çağrımları sırasında araya girerek bizlerin çakışan ilgilerimizi işletmemizi ve yönetmemizi sağlamakta. Böylece metotların çalışmasından önce veya sonra bir takım işlemleri gerçekleştirebilmekteyiz. Şimdi gelin artık kod tarafına,  yani işin eğlenceli kısmına geçiş yapalım. :)

Bu arada Aspect Oriented Programming konusu yukarıda okuduğumuz gibi tek başlıkta geçilebilecek bir konu değil. Pek çok farklı noktaları bulunmakta. Bu nedenle Aspect Oriented Programming konusu ile ilgili daha fazla bilgi edinmek isterseniz Arda Çetinkaya’nın bu konuda yazmış olduğu makeleleleri okuyabilirsiniz.

Interceptors

Şimdi sıra geldi yukarıda bahsetmiş olduğumuz metotların çalışması sırasında araya  girecek olan interceptor yapısının nasıl geliştirileceğine. Aslında bu şekilde bir yapı geliştirmek için .Net Framework içerisinde bir takım tipler bulunmakta. Ancak baktığımızda hızlı bir şekilde geliştirme yapmak bu tipler ile oldukça güç ve acı çektirici :) Bunun yerine gerek Microsoft Enterprise Library içerisinde bulunan Unity bloğu olsun gerekse Castle Windsor gibi kütüphaneler olsun bizlere Interceptor yapısını sağlamak için bir takım kütüphaneler sunmaktalar. Biz bu yazımızda öncelikle daha önceki IoC Containers yazımızda olduğu gibi Castle Windsor kütüphanesi üzerinden ilerleyeceğiz.

Yazımız boyunca ilerleyeceğimiz örnek ise yine önceki IoC yazılarımızda kullandığımız yapı üzerinden olacak. Bu nedenle öncelikle buradaki adresten yaptığımız örneğin son halini indirebilirsiniz. Uygulamayı indirdiğinizde aşağıdaki gibi bir yapı ile karşılaşıyor olacaksınız.

IoCExample

Önceki yazılarımızda gerçekleştirdiğimiz uygulamayı tekrardan kısaca açıklamamız gerekirse, uygulamamız içerisinde bulunan Processor tipi içerisinde business işlemlerinin yönetimi yapılmakta. Ayrıca IoC kullanarak gerçekleştirdiğimiz bağımlılıkların konfigüre edilmesi özelliği sayesinde Processor metodu IoC Container tarafından sunulan tipleri kullanarak ilgili çağrımları gerçekleştirmekte. (IoC ile ilgili detaylı bilgi için buradaki yazımı okuyabilirsiniz.)

Şimdi gelin mevcut uygulamamıza Interceptor yapısı ekleyelim :)

Interceptor Gerçekleştirimi

Castle Windsor kullanarak Interceptor geliştirmek için bilmemiz gereken bir nokta bulunmakta. Bu nokta da Interceptor metodunu içerisinde bulunduran tipin IInterceptor interface’ini implemente etmesi gerektiği. Şimdi mevcut uygulamamız içerisinde Interceptor isimli bir sınıf yaratalım ve IInterceptor interface’ini implemente ettirelim.

public class Interceptor : IInterceptor     
{
    #region IInterceptor Members         
    public void Intercept(IInvocation invocation)
    {
          throw new NotImplementedException();
    }
 
    #endregion
}

 

Evet aslında gördüğünüz gibi herşey oldukça basit. İşte size Interceptor metodumuz. :) Burada artık istediğimiz işlemleri gerçekleştirebiliriz. IInterceptor interface’i içerisinden gelen Intercept metodu gördüğünüz gibi IInvocation tipinden bir parametre almakta. Bu parametre aslında esas olarak çağrılan metot ile ilgili tüm bilgileri içerisinde barındırmakta. Bu nedenle metot çağrımından önce yazacağımız interceptor devreye gireceğinden dolayı çağrılan metodu çalıştırmak yada çalıştırmamak bizim elimizde. Bunun için de eğer istek yapılan metodu çağırmak istersek IInvocation tipi içerisinde bulunan Proceed metodunu çağırmamız gerekmekte.

Şimdi isterseniz interceptor metodunu biraz daha anlamlı hale getirelim.

public void Intercept(IInvocation invocation)
{
  try
  {
    //Metoda gelen ilk isteği logluyoruz.
    Console.WriteLine("{0} isimli metoda istek geldi}", invocation.Method.Name);
    
   //İsteği yapan kullanıcının işlemi yapma yetkisi var mı yok mu 
   //kontrolünü burada yapabiliriz.

   //Eğer yetkisi varsa metodu çalıştır.
     invocation.Proceed();
   
   // Yoksa Exception fırlatılabilir.

   Console.WriteLine("{0} isimli metodun çalışması sona erdi.}", 
                     invocation.Method.Name);             
  }
  catch (Exception ex)
  {
    Console.WriteLine("Hata oluştu {0}",ex.ToString());
  }
}

 

Şu anda biraz daha anlamlı bir Interceptor metodunu gerçekleştirmiş durumdayız. Şimdi sıra geldi yazdığımız interceptor metodunu Castle Windsor tarafına bildirmeye. IoC Container kısmından da hatırlayacağımız gibi Castle Windsor konfigürasyon için hem fluent bir arayüz hem de config dosyası desteği sunmakta. Biz ilk olarak fluent arayüz implementasyonunu gerçekleştireceğiz.

Mevcut uygulamamıza baktığımızda IoCUtil sınıfı içerisinde zaten fluent olarak IoC tanımlamalarının yapıldığını görmekteyiz. Bizim ek olarak yapmamız gereken interceptor tanımlamamızı yapmak. Bunun içinde yapmamız gereken ilk şey Interceptor’ı container içerisine register etmek ve yaşam süresini belirlemek. Bu şekilde Interceptor sınıfının kullanımını belirleyeceğiz. Daha sonrasında ise tiplerimiz üzerinde araya girecek olan Interceptor’ı yine container üzerinden bildireceğiz.

private static IWindsorContainer BootstrapContainer()
{    return new WindsorContainer().Register(        
               Component.For<Interceptor>().LifeStyle.Transient,
               Component.For<IMessageSender>().ImplementedBy<MailSender>()
                         .Interceptors<Interceptor>(),
               Component.For<ILogger>().ImplementedBy<DBLogger>()
                         .Interceptors<Interceptor>()   
        );
}

 

Yukarıdaki bildirimde ilk olarak Interceptor tipimizin yaşam süresi ile ilgili bir bildirim yaptık. Burada yaptığımız Transient bildirimi ile bu tipin her resolve sırasında tekrardan yaratılacağını bildirdik. Daha sonra ise IoC tanımlamalarımız sırasında bu tipler üzerinden yapacağımız çağrılarda geliştirmiş olduğumuz Interceptor tipini kullanacağımızı bildirmiş olduk.

Şimdi ise uygulamamızı çalıştıralım ve Interceptor’ın çalışmasını gözlemleyelim.

Interceptor

Gördüğümüz gibi Interceptor devreye girdi ve gerekli çıktıları ekrana yazdı. Şimdi de config dosyası tarafından tanımlamalara bakalım.

Config Dosyası Kullanılarak Interceptor Register Etme

Config dosyasından yapacağımız register işleminde de aslında fluent olarak izlediğimiz yaklaşımla aynı olacak. Öncelikle Interceptor tipimizi register ettikten sonra register ettiğimiz tipi interceptor olarak ilgili IoC tanımlamalarımıza eklememiz gerekmekte.

Bu işlemleri gerçekleştiren XML konfigürasyonu aşağıdaki gibi olacak.

<castle>
    <components>
      <component id="logger"
     service="Windsor.Project.Interfaces.ILogger, Windsor.Project"
     type="Windsor.Project.Loggers.DBLogger, Windsor.Project">
        <interceptors>
          <interceptor>${interceptor}</interceptor>
        </interceptors>
      </component>
      <component id="repository"
     service="Windsor.Project.Interfaces.IMessageSender, Windsor.Project"
     type="Windsor.Project.MessageSenders.SMSSender, Windsor.Project">
        <interceptors>
          <interceptor>${interceptor}</interceptor>
        </interceptors>
      </component>
      <component
      id="interceptor"
      type="Windsor.Project.Interceptor, Windsor.Project"
      lifestyle="transient">
      </component>
    </components>
  </castle>

Bitirirken

Evet arkadaşlar bu yazımızda da yine uygulamalarımızı geliştirme sırasında kullanmış olduğumuz interceptor yapısını incelemeye çalıştık. Interceptorlar bir çok noktada bizlerin yazmış olduğu kodları oldukça kısaltmakta ve kodu daha temiz hale getirmekte. Bunun yanında yukarıda bahsetmiş olduğum bir takım işlemleri interceptor tarafına taşıyabileceğiniz gibi ayrıca bizim son uygulamamızda kulllandığımız üzere dinamik olarak farklı assemblyler üzerinden reflection kullanarak özelleşmiş metot çağrımları gibi senaryoları  da gerçekleştirebilirsiniz. Aslında burada önemli olan bu gibi imkanları gerektiği durumlarda nokta vuruşu yaparak kullanmak. ;)

Yazımızda yaptığımız örneği aşağıdan indirebilirsiniz.

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

Hoşçakalın

Windsor.Project.rar (712,90 kb)

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

C# Iterators,Yield Keywordü ve Arka Planda Neler Oluyor ?

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

Merhaba Arkadaşlar,

Bu yazımızda sizlerle C# tarafındaki iterator yapısını incelemeye çalışacağız. Iteratorlar, array veya koleksiyonlar üzerinde yield keyword’ü kullanılarak içerlerindeki elemanlar üzerinde özel iterasyonlar yapmamızı sağlayan yapılardır. Iterator’lar bir metot olarak geliştirilebileceği gibi aynı zamanda bir property’nin get erişimcisi olarak ya da bir operator olarak ta geliştirilebilmektedir.

C# tarafında aslında iterasyon dediğimizde aklımıza gelen ilk yapı foreach döngüleri olmakta. foreach döngüleri ile bir kolleksiyon veya array içerisindeki elemanlara erişebiliyoruz ve istediğimiz işlemleri gerçekleştirebiliyoruz. Foreach döngüsünün arka planına baktığımızda da aslında bir iterator yapısının kullanıldığını görüyor olacağız. Çünkü iteratorlar foreach döngüleri tarafından kullanılırlar ve işlem yaptıkları kolleksiyonlar veya arrayler içerisindeki elemanları foreach döngüsüne iletirler.

Bir iterator yaratmak için öncelikli olarak bilmemiz gereken iteratorların dönüş tiplerinin IEnumerable, IEnumerator, IEnumerable<T> veya IEnumerator<T> interfacelerinden biri olması gerektiğidir. Zaten foreach döngüsünün yapısına aşina iseniz bu kuralı biliyor olduğunuzu düşünüyorum. ;) Iteratorlar ile ilgili bilmemiz gereken ikinci ve bence en önemli nokta yield keyword’ü. Bu keyword için özel bir başlık açmamızda fayda var. :)

Yield Keyword’ü Nedir ? Ne Yapar ?

Yield keyword’ünün kullanımına baktığımızda aslında arkasında bir compiler sihrinin yattığını görüyoruz. Bu nedenle de belki de yield keyword’ü genel olarak pek fazla developer tarafından bilinmemekte. Hatta bunu bilen şirketler iş görüşmelerinde yield keyword’ünün işlevini adaylara soru olarak yöneltmekte. :)

Yield keyword’ünün kullanımını incelediğimizde iki farklı kullanım şeklinin olduğunu görmekteyiz.

Bunlar,

  • yield return <ifade>
  • yield break

Yield return ifadesi ile iterator’a çağrı yapılan foreach döngüsüne bir eleman döndürülürken yield break ifadesi ile de artık bulunan iterator içerisindeki iterasyonun sona erdiği bilgisi iterator’ı çağıran foreach döngüsüne iletilmekte. Şimdi isterseniz basit bir iterator metot geliştirelim ve foreach döngüsü ile de geliştirdiğimiz iterator’ı kullanalım.

İlk olarak basit bir örnekten başlamak adına bize haftanın günlerini döndüren bir iterator metot geliştirelim.

static IEnumerable<string> GetDays()
{
    yield return "Pazartesi";
    yield return "Salı";
    yield return "Çarşamba";
    yield return "Perşembe";
    yield return "Cuma";
    yield return "Cumartesi";
    yield return "Pazar";
}

Geliştirdiğimiz iterator metodunu kullanacak olan foreach döngüsü ise şu şekilde

static void Main(string[] args)
{
   foreach (string day in GetDays())
   {
        Console.WriteLine(day);             
   }
}

Şimdi Console uygulaması olarak geliştirdiğimiz uygulamayı çalıştıralım ekranda oluşan çıktıları inceleyelim.

İşte yield keyword’ünün tüm sihri bu ekran görüntüsü ile başlamakta. Yukarıda yazmış olduğumuz GetDays isimli metodunun içerisine baktığımızda alt alta yield return ifadesini kullandığımızı görmekteyiz. Aslında var olan bilgilerimiz ile return ifadesinin bu şekilde kullanılmasının mümkün olmadığını biliyoruz. Peki ne oluyor da return ifadesinin önüne yield keyword’ü geldiğinde bu kullanım geçerli oluyor ? İşte bu sorunun altında aslında yield keyword’ünün işlevi yatıyor.

Compiler yield keyword’ünü gördüğü anda bu keyword’ün bulunduğu bloğun bir iterator bloğu olduğunu algılamakta. İşte bu noktada foreach döngüsü tarafından GetDays isimli iterator metodu her çağrıldığında metot içerisindeki bir yield return ifadesi çalıştırıldıktan sonra ilgili değer foreach ifadesine döndürülmeden önce compiler, iterator metodun kaldığı yeri saklamakta. Daha sonra ise foreach ifadesinden iterator metoduna gelen her istekte iterator metot baştan başlamak yerine kaldığı yerden işletilmekte. Böylece de yukarıdaki ekran görüntüsünde gördüğünüz sonuçları alabilmekteyiz.

Şimdi sıradaki örneğimiz ise aslında biraz daha gerçek kullanıma uygun. Senaryomuzda 10 elemanlı bir integer array’i bulunmakta. Uygulamamızı çalıştırmadan önce bu array’i 1 ile 30 arasındaki sayılar ile rastgele dolduracağız. Daha sonra ise geliştireceğimiz iterator ile bu array içerisinde bulunan ilk 2 sıradaki çift sayıları elde edeceğiz. Iterator’ı kullanan foreach döngüsüne ikinci kez çift sayısı döndürdükten sonra ise iterasyonun sonuna geldiğimizi foreach döngüsüne bildiriyor olacağız. Öncelikle integer array’i rastgele sayılarla dolduracak olan metodun geliştirimini yapalım.

private static void FillArray()
{
    Random rnd = new Random();
    numbers = new int[10];
    for (int i = 0; i < numbers.Length; i++)
    {
        numbers[i] = rnd.Next(1, 30);
    }
}

 

Array içerisinde bulunan ilk 2 sıradaki çift sayıları elde edeceğimizi iterator metodun geliştirimi ise şu şekilde.

static IEnumerable<int> GetTwoEvenNumbers()
{
   int evenNumber = 0;
   for (int i = 0; i < numbers.Length; i++)
   {
       if (evenNumber == 2)
           yield break;
       if (numbers[i] % 2 == 0)
       {
           evenNumber++;
           yield return numbers[i];
       }
   }
}

 

Son olarak ise iterator'ı kullanacak olan foreach ifadesinin de bulunduğu Main metodumuzu geliştirelim ve uygulamayı çalıştıralım.

static int[] numbers = null;
static void Main(string[] args)
{
   FillArray();
   foreach (int number in GetTwoEvenNumbers())
   {
       Console.WriteLine(number);
   }
}

Evet gördüğünüz gibi iterator metodu ile istediğimiz sonuçları elde edebildik. Şimdi isterseniz gelin yazmış olduğumuz GetTwoEvenNumbers isimli metodu inceleyelim. Metot içerisinde öncelikle iterator bloğu içerisinden döndürmüş olduğumuz çift sayıların sayısının tutmak amacıyla evenNumber isimli bir değişken tanımladık. Bu değişkenin değerini array içerisindeki çift sayıyı yield return ile döndürmeden hemen önce bir arttırdık. Son olarak ise for döngüsü içerisinde döndürdüğümüz çift sayıların sayısının kontrolünü yaptık ve eğer çift sayıların sayısı 2 ise iterasyonun sonlandırılması gerektiğini anlayıp yield break ifadesini kullandık.

Yield keyword’ünün kullanımını daha iyi kavradıkça aslında uygulamalarımız içerisinde pek çok noktada yield keyword’ünü kullanabileceğinizi farkediyor olacaksınız. Ayrıca şu anda .NET Framework içerisinde bulunan LINQ ifadelerindeki deferred execution yapısının alt yapısında iteratorların ve dolayısıyla da yield keyword’ünün yattığını söyleyebilirim.

Yazımızın bu kısmına kadar sizlerle iteratorları ve yield keyword’ünün işlevini incelemeye çalıştık. Ancak yield keyword’ünün bulunduğu bölümlerde gördüğünüz üzere bizim bildiğimiz akışlardan farklı bir akış var. Peki bu nasıl mümkün oluyor ? Şimdi bu konuyu incelemeye çalışalım.

Yield – Arka Planda Neler Oluyor ?

Yazımızın başında yield keyword’ünün arkasında aslında bir compiler sihrinin yattığından bahsetmiştim. Şimdi isterseniz bu sihri keşfetmeye başlayalım. Keşif için yazımızın başındaki günleri döndüren iterator’ı kullanıyor olacağız. Öncelikle isterseniz keşfimizi biraz daha kolaylaştırmak için iterator metot içerisinde 1-2 ufak değişiklik yapalım.

static IEnumerator GetDays()
{
    Console.WriteLine("Hafta başlıyor");
    yield return "Pazartesi";
    yield return "Salı";
    yield return "Çarşamba";
    Console.WriteLine("Hafta ortası");
    yield return "Perşembe";
    yield return "Cuma";
    yield return "Cumartesi";
    yield return "Pazar";
    Console.WriteLine("Hafta sonlanıyor...");
}

 

Yazımızın ilk paragraflarında bir iterator’ın dönüş tipinin IEnumerable, IEnumerable<T>, IEnumerator veya IEnumerator<T> interfacelerinden biri olması gerektiğinden bahsetmiştik. Biz ilk olarak IEnumerator tipini döndüren iterator metodunu inceliyor olacağız. Şimdi kodumuzu derleyelim ve Reflector aracılığıyla compiler’ın bizim için neler yaptığını incelemeye başlayalım. :)

internal class Program
{
    // Methods
    private static IEnumerator GetDays()
    {
        return new <GetDays>d__0(0);
    }

     // Nested Types
    [CompilerGenerated]
    private sealed class <GetDays>d__0 : IEnumerator<object>, IEnumerator, IDisposable
    {
        // Fields
        private bool $__disposing;
        private int <>1__state;
        private object <>2__current;

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

        private bool MoveNext()
        {
            try
            {
                bool $__doFinallyBodies = true;
                switch (this.<>1__state)
                {
                    case 1:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Salı";
                        this.<>1__state = 2;
                        $__doFinallyBodies = false;
                        return true;

                    case 2:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "\x00c7arşamba";
                        this.<>1__state = 3;
                        $__doFinallyBodies = false;
                        return true;

                    case 3:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        Console.WriteLine("Hafta ortası");
                        this.<>2__current = "Perşembe";
                        this.<>1__state = 4;
                        $__doFinallyBodies = false;
                        return true;

                    case 4:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Cuma";
                        this.<>1__state = 5;
                        $__doFinallyBodies = false;
                        return true;

                    case 5:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Cumartesi";
                        this.<>1__state = 6;
                        $__doFinallyBodies = false;
                        return true;

                    case 6:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Pazar";
                        this.<>1__state = 7;
                        $__doFinallyBodies = false;
                        return true;

                    case 7:
                        break;

                    case -1:
                        return false;

                    default:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        Console.WriteLine("Hafta başlıyor");
                        this.<>2__current = "Pazartesi";
                        this.<>1__state = 1;
                        $__doFinallyBodies = false;
                        return true;
                }
                if (this.$__disposing)
                {
                    return false;
                }
                this.<>1__state = 0;
                Console.WriteLine("Hafta sonlanıyor...");
            }
            catch (Exception)
            {
                this.<>1__state = -1;
                throw;
            }
            this.<>1__state = -1;
            return false;
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

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

        // Properties
        object IEnumerator<object>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
}

Evet şu anda şok olmuş durumda olduğunuzu düşünüyorum. Nasıl oluyorda yazdığımız 10 satırlık metot bu şekilde compiler tarafından yeniden yazılıyor. İşte işin sihri burada yatıyor. Haydi biraz daha derinlere inelim.

  • Öncelikle compiler tarafından yarattığımız iterator metot için <GetDays>d__0 isimli bir inner –class yaratılıyor.
  • Yaratılan inner-class’ın implemente ettiği interface’leri incelediğimizde IEnumerator,IEnumerator<object> ve IDisposable olduğunu görüyoruz. foreach döngüsü sonlandıktan sonra otomatik olarak IDisposable interface’i içerisinde bulunan Dispose metodunu çağırmakta. Böylece döngünün kullanmış olduğu iteratorın otomatik olarak yok edilmesi sağlanmakta. Ayrıca developerların using ifadesi kullanarak otomatik olarak kendi kontrollerinde kullandıkları iteratorları bu şekilde yok etmeleri de mümkün hale gelmekte.
  • Ayrıca yaratmış olduğumuz iterator metodumuzun gövdesinin tamamen kaldırıldığını ve sadece compiler tarafından yaratılmış olan sınıftan bir nesne örneği yaratıp döndürecek şekilde yeninden yazıldığını görüyoruz. Bu arada nesne örneği yarattığı sırada parametre olarak 0 gönderdiğini de mutlaka aklımızda tutalım ;)
  • Yaratılan inner-class içerisinde 3 adet private değişken görmekteyiz. Bu değişkenler sırasıyla iterator’ın dispose edilme durumunda olup olmadığını, iterator’ın durumunu ve o anda iterator içerisindeki mevcut elemanı tutmakta.
  • Constructor metot içerisinde dışarıdan alınan parametreye göre ise state değişkenine ilk değer ataması yapılmakta.
  • Ve gelelim MoveNext metoduna. Gördüğünüz gibi MoveNext metodu kocaman bir switch-case ifadesi. Şimdilik bu ifadeye çok detaya girmeden yüksek seviye bir bakış yaparsak temel olarak içerisinde bulunan durumlara göre çeşitli işlemler yaptığını görüyoruz.
  • IEnumerator.Reset metodu NotSupportedException döndürmekte.
  • Son olarak ise IEnumerator ve IEnumerator<object> interfacelerinden gelen Current propertyleri class içerisinde tanımlı olan mevcut elemanı döndürmekte.

Genel olarak yapıyı incelediğimizde compiler’ın aslında arka planda bir state – machine(durum makinası) yarattığını görmüş oluyoruz. İçerisinde bulunduğu mevcut durumlara göre akış çeşitli yerlere dallanmakta ve ilgili eleman current propertysine atanmakta. Şimdi gelin örneğimizi bu sefer IEnumerator’ın generic versiyonuna göre güncelleyelim. Buna göre iterator metodumuzun imzası şu şekilde olacak.

static IEnumerator<string> GetDays()

 

Şimdi kodumuzu yeniden derleyelim ve reflector ile tekrardan açalım. Bakalım ne gibi değişiklilkler oldu.

private sealed class <GetDays>d__0 : IEnumerator<string>, IEnumerator, IDisposable

İlk değişiklik compiler tarafından yaratılan inner-class’ın imzasında meydana geldi. Generic versiyonu kullanmadığımız durumda class’ın implemente ettiği IEnumerator interface’inin generic parametresi object iken bu kez string oldu.

Bunun yanında oluşan bir diğer değişiklik ise inner-class içerisinde mevcut elemanı tutan current değişkeninde ve IEnumerator interface’i tarafından gelen Current propertysinde.

private string <>2__current;
string IEnumerator<string>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }

Gördüğünüz gibi 2 alanında tipleri object’ten string’e döndü.

Şimdi sırada aynı örneğin IEnumerable<string> döndüren versiyonu var. Metodumuzun imzasını şu şekilde değiştiriyoruz.

internal class Program
{
    // Methods
    private static IEnumerable<string> GetDays()
    {
        return new <GetDays>d__0(-2);
    }

    // Nested Types
    [CompilerGenerated]
    private sealed class <GetDays>d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable
    {
        // Fields
        private bool $__disposing;
        private int <>1__state;
        private string <>2__current;
        private int <>l__initialThreadId;

        // Methods
        [DebuggerHidden]
        public <GetDays>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        private bool MoveNext()
        {
            try
            {
                bool $__doFinallyBodies = true;
                switch (this.<>1__state)
                {
                    case 1:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Salı";
                        this.<>1__state = 2;
                        $__doFinallyBodies = false;
                        return true;

                    case 2:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "\x00c7arşamba";
                        this.<>1__state = 3;
                        $__doFinallyBodies = false;
                        return true;

                    case 3:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        Console.WriteLine("Hafta ortası");
                        this.<>2__current = "Perşembe";
                        this.<>1__state = 4;
                        $__doFinallyBodies = false;
                        return true;

                    case 4:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Cuma";
                        this.<>1__state = 5;
                        $__doFinallyBodies = false;
                        return true;

                    case 5:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Cumartesi";
                        this.<>1__state = 6;
                        $__doFinallyBodies = false;
                        return true;

                    case 6:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        this.<>1__state = 0;
                        this.<>2__current = "Pazar";
                        this.<>1__state = 7;
                        $__doFinallyBodies = false;
                        return true;

                    case 7:
                        break;

                    case -1:
                        return false;

                    default:
                        if (this.$__disposing)
                        {
                            return false;
                        }
                        Console.WriteLine("Hafta başlıyor");
                        this.<>2__current = "Pazartesi";
                        this.<>1__state = 1;
                        $__doFinallyBodies = false;
                        return true;
                }
                if (this.$__disposing)
                {
                    return false;
                }
                this.<>1__state = 0;
                Console.WriteLine("Hafta sonlanıyor...");
            }
            catch (Exception)
            {
                this.<>1__state = -1;
                throw;
            }
            this.<>1__state = -1;
            return false;
        }

        [DebuggerHidden]
        IEnumerator<string> IEnumerable<string>.GetEnumerator()
        {
            if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
            {
                this.<>1__state = 0;
                return this;
            }
            return new Program.<GetDays>d__0(0);
        }

        [DebuggerHidden]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.System.Collections.Generic.IEnumerable<System.String>.GetEnumerator();
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

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

        // Properties
        string IEnumerator<string>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
}

Iterator metodumuzun dönüş tipini IEnumerable<string> yapınca compiler tarafında yapılan değişikliklerin sayısı biraz arttı.

Şimdi bu değişiklikleri sıralayalım.

  • İlk olarak farkettiyseniz yazmış olduğumuz iterator metot içerisinden yarattılan inner-class örneğinin constructor’ına artık 0 değil –2 parametresi gidiyor. Yani ilk durumda state değişkeni –2’ye çekiliyor.
  • Compiler tarafından üretilen inner-class içerisinde iterator’ı yaratan thread’in Id’sini tutmak üzere initialThreadId isimli bir değişken eklenmiştir.

Sonuç olarak baktığımızda compiler yarattığımız iterator bloklarını arka planda tamamen yeniden yazıp bir state-machine haline getiriyor ve foreach ifadeleri üzerinde yapılan çağrıların bu state-machine içerisinde çalışması sağlanıyor. Şimdi gelin compiler tarafından yaratılan state machine mekanizmasının derinlerine dalalım ve iterator’a gelen istekler nasıl değerlendiriliyor ve state-machine içerisinde durumlar arası nasıl geçiş yapılıyor inceleyelim.

Compiler Tarafından Yaratılan State-Machine

Yukarıdaki paragraflardaki incelemelerimizde compiler tarafından yaratılan inner-class içerisinde bir state-machine implementasyonu yapıldığını gördük. Bu yaratılan state-machine iterator’a yapılan tüm çağrılarda kullanılmakta ve işletilmekte. İşletilen durumlara göre de makinanın durumu değişmekte ve değişen duruma göre de farklı bir akış işletilmekte. Şimdi en baştan başlayarak state-machine içerisinde yapılan işlemleri ve mevcut durumlarını inceleyelim.

İlk olarak iterator’a ilk istekte bulunulduğunda öncelikli olarak bizim mevcutta yazmış olduğumuz GetDays isimli metot çağrılmakta ve bir IEnumerable<string> nesne örneği talep edilmekte. Bu metot içerisinde de compiler tarafından yaratılmış olan sınıfın örneklenmesi yapılmakta ve constructor içerisinde –2 değeri parametre olarak geçirilmekte. Bu nedenle iterator’ın ilk yaratıldığında bulunduğu durum –2 durumudur. Ayrıca yine constructor içerisinde iterator’ı yaratan thread’in Id değeri de local değişkene atanarak bu değer ileride kullanılmak üzere saklanmakta.

Sonrasında ise  foreach döngüsü tarafından iterator içerisindeki GetEnumerator metodu çağrılmakta. Bu metot içerisinde ise eğer çağrıyı yapan thread’in Id değeri mevcut iterator’ı yaratan yani yukarıdaki paragrafta yaratma görevini yapan thread ile aynı ise state –machine ‘in durumu 0 durumuna çekilmekte ve mevcut sınıf foreach döngüsüne dönülmekte. Eğer bu durum sağlanmıyor ise mevcut sınıfın bir örneği yaratılarak yeni yaratılan state-machine’in durumu 0’a getiriliyor. Böylece de farklı threadler üzerinden kullanılan iterator’ların her birinin değerlerinin ve durumlarının saklanması bu şekilde mümkün olabilmekte.

Bu noktadan sonra ise artık foreach tarafından Enumerator üzerinden MoveNext metodu çağrıları gerçekleşmekte. Bu çağrılar sırasında ilk durumumuz 0 durumuydu. Bu durumda switch-case ifadesinde doğrudan default etiketi işletilecektir. Bu durumda ilgili değer current değişkenine atandıktan sonra state-machine’in durumu 1’e getirilmekte.

Sonrasında ise foreach döngüsü tarafından yapılan diğer MoveNext çağrılarında ise yine compiler tarafından yazılmış olan case ifadeleri içerisinde öncelikle makinanın durumu 0’a çekilmekte. Akabinde ise sıradaki eleman current değişkenine aktarılırken sonrasında ise durumun değeri bir sonraki case ifadesinin değerine geçiriliyor. state değişkeninin değeri 7 oluncaya kadar bu paragrafta bahsetmiş olduğumuz akış işletilmekte.

State değeri 7 olduğunda ise gördüğünüz gibi sadece break ifadesi bulunmakta. Böylece aslında case içerisinde hiç bir işlem yapılmadan çıkılmakta. Sonrasında ise son olarak yazdığımız “Hafta sonlanıyor” ifadesini yazımı için state değişkeni 0’a çekilerek ilgili kod işlendikten sonra makinanın durumu son olarak –1’e çekiliyor ve bu sefer MoveNext metodundan false değer geriye döndürülüyor. Makinanın durumunun –1’e çekilmesiyle durum makinasının artık sona geldiğini görmekteyiz.

Ayrıca MoveNext metodu içerisinde try-catch bloğunun da yaratıldığını görmekteyiz. Böylece try bloğu içerisinde bir exception olduğu durumda durum makinasının durumu otomatik olarak –1’e çekilerek makinanın son durumuna durumuna dönmesi sağlanmakta.

Şimdi isterseniz durum makinası içerisindeki durumların değerlerini kısaca özetleyelim.

-2 : Bu durum IEnumerable örneğinin yaratıldığı ilk durumdur. Bu durumda henüz IEnumerator içerisinde GetEnumerator isimli metot çağrılmamıştır.

0 : Bu durum içerisinde GetEnumerator metodu çağrılarak ilgili Enumerator foreach ifadesine dönülmüştür. Bu durumda MoveNext metodunda switch-case ifadesinin default etiketi işletilir.

1-7 : Bu durumlar yazmış olduğumuz yield ifadelerinin akışını temsil eder. İlgili değerler current değişkenine atandıktan sonra durum değerleri 1 arttırılır.

-1 : Bu durumda iterator’ın sonuna gelinmiştir ve daha erişilecek eleman kalmamıştır.

Şimdi isterseniz yazımızın başında incelediğimiz diğer örneğimiz olan bir array içerisindeki ilk iki sıradaki çift sayıyı döndüren iterator metodunun arka planını inceleyelim.

internal class Program
{
    // Fields
    private static int[] numbers = null;

    private static IEnumerable<int> GetTwoEvenNumbers()
    {
        return new <GetTwoEvenNumbers>d__0(-2);
    }

    private static void Main(string[] args)
    {
        FillArray();
        foreach (int number in GetTwoEvenNumbers())
        {
            Console.WriteLine(number);
        }
    }

    // Nested Types
    [CompilerGenerated]
    private sealed class <GetTwoEvenNumbers>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
    {
        // Fields
        private bool $__disposing;
        private int <>1__state;
        private int <>2__current;
        private int <>l__initialThreadId;
        public int <evenNumber>5__1;
        public int <i>5__2;

        // Methods
        [DebuggerHidden]
        public <GetTwoEvenNumbers>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        private bool MoveNext()
        {
            try
            {
                bool $__doFinallyBodies = true;
                if (this.<>1__state == 1)
                {
                    goto Label_00B9;
                }
                if (this.<>1__state == -1)
                {
                    return false;
                }
                if (this.$__disposing)
                {
                    return false;
                }
                this.<evenNumber>5__1 = 0;
                this.<i>5__2 = 0;
                while (this.<i>5__2 < Program.numbers.Length)
                {
                    if (this.<evenNumber>5__1 == 2)
                    {
                        goto Label_0108;
                    }
                    if ((Program.numbers[this.<i>5__2] % 2) != 0)
                    {
                        goto Label_00D4;
                    }
                    this.<evenNumber>5__1++;
                    this.<>2__current = Program.numbers[this.<i>5__2];
                    this.<>1__state = 1;
                    $__doFinallyBodies = false;
                    return true;
                Label_00B9:
                    if (this.$__disposing)
                    {
                        return false;
                    }
                    this.<>1__state = 0;
                Label_00D4:
                    this.<i>5__2++;
                }
            }
            catch (Exception)
            {
                this.<>1__state = -1;
                throw;
            }
        Label_0108:
            this.<>1__state = -1;
            return false;
        }

        [DebuggerHidden]
        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
            {
                this.<>1__state = 0;
                return this;
            }
            return new Program.<GetTwoEvenNumbers>d__0(0);
        }

        [DebuggerHidden]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

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

        // Properties
        int IEnumerator<int>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
}

Sanki compiler’ın yeniden yazım şekli biraz değişti gibi :) Bunun nedeni ise aslında durum makinasının akışının çok belirgin olmaması. Çünkü ilk örneğimizde sırayla yield ifadeleri kullanırken bu sefer belirli koşullara uygun olarak iterasyonumuzu devam ettirmekteyiz ve bazı koşullara göre de iterasyonu sonlandırmaktayız.

Ayrıca compiler tarafından yazılan kodlara baktığımızda goto ifadelerine rastlamaktayız. Pratikte biz geliştiricilerin goto kullanması çokta tavsiye edilmezken compiler tarafından yazılan kodlara baktığımızda sıklıkla goto ifadelerini görürüz. Çünkü goto ifadeleri ile compiler optimizasyonu yapmak ve bir takım kodların akışının kontrol edilmesi oldukça kolaylaşmakta.

Bunların yanında compiler tarafından yaratılan sınıfın neden bir inner-class olduğunu merak ettiğinizi düşünüyorum. :) Bunun nedeni de aslında oldukça basit. Bulunduğu dış sınıf içerisinde bulunan private değişkenler dahil tüm değişkenlere erişebiliyor olabilmesi.

MoveNext metodunun yapısına baktığımızda aslında bu sefer switch-case yapısının olmadığını görüyoruz. Bunun yerine bir while döngüsü ile array içerisindeki elemanlar içerisinde iterasyon yapılmakta. While döngüsü içerisinde öncelikle iterasyondan döndürülen çift sayıların sayısı kontrol edilmekte. Eğer bu değer 2’ye eşit ise goto ifadesi ile ilgili label’a atlanılarak buradan durum makinasının durumunun –1’e çekilmesi sağlanmakta. Eğer sayı 2’den farklı ise sayının 2’ye tam bölünüp bölünmediği kontrol edilmekte. Eğer sayı tam bölünemiyor ise while döngüsü içerisindeki index değeri 1 arttırılıyor. Eğer sayı tam bölünebilen bir sayı ise de bu değer öncelikle current değişkenine atanıyor ve makinanın durumu 1’e çekiliyor.

Makinanın durumunun 1’e çekilmesi aslında makinanın işletimde olduğunu göstermekte. Böylece MoveNext metodu her çağrıldığında öncelikli olarak akış while döngüsü içerisinde bulunan bir label’a yönlenmekte. Sonrasında ise bu label içerisinde makinanın durumu 0'a çekilmekte ve while döngüsündeki kontroller baştan işletilmekte.

Bitirirken

Evet arkadaşlar bu yazımızda aslında C# içerisinde uzun süredir bulunan ancak pek fazla bilinmeyen ancak oldukça faydalı ve sihirli bir keyword olan yield keyword’ünü inceledik. Aslında baktığımızda 10 satır gibi kısa bir kod parçasıyla işimizi hallederken aslında compiler’ın arka planda nasıl bir görevi üstlendiğini görmüş olduk.

Eğer önceki yazılarımda daha önce denk geldiyseniz zaman zaman compilerlara olan hayranlığımdan bahsetmişimdir. Çünkü arka planda gerçekten mükemmel işler yapıyorlar. Bu nedenle bence özel bir takdir’i hak ediyorlar. Bunun yanında biz developerlarında aslında bu detaylara biraz aşina olmasında yarar var. Çünkü ancak bu şekilde en doğru kullanımları gerçekleştirebiliriz.

İlerleyen yazılarda zaman zaman sizlerle yine C# compilerı tarafında arka planda ne gibi işler döndüğünü paylaşmaya çalışacağım. :)

Bu arada bu yazımı okuyan VB developerlar VB’de Iterator’ların olup olmadığını merak ediyor olabilirler. Iterator’lar C# ve VB’nin özelliklerinin eşlenmesi kapsamı içerisinde VB’nin bir sonraki versiyonu olan VB.NET 11 içerisinde geliyor olacak. VB için detaylı bilgiyi buradan elde edebilirsiniz.

Hoşçakalın

Tags:   ,
Categories:   C#
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

IoC Containers ve Castle Windsor IoC Container Kullanımı

Perşembe, 24 Kasım 2011 22:49 by ilkayilknur

Merhaba Arkadaşlar,

Önceki yazılarımızdan birinde sizlerle Dependency Injection konusunu incelemiştik. O yazıdan sonra gerek bloga yorum olarak gerekse mail yoluyla sizlerden pek çok olumlu feedbackler aldım. Bu nedenle IoC konusunda gerek kurumsal tarafta kullandığımız pratik kütüphanelerden olsun gerekse kişisel olarak kullandığım bazı kütüphanelerden bu yazıda ve ilerleyen yazılarda bahsetmeye karar verdim. Umarım sizler için faydalı olur.

Önceki yazılarımızdan da hatırlayacağımız üzere Dependency Injection, bizim genel olarak Inversion of Control adını verdiğimiz uygulama akışının çalışma zamanında değişmesini sağlayan ve uygulama içerisindeki bağımlılıkları minimuma indirgemeyi de amaçlayan prensibin özelleşmiş hatta bir pattern halini almış bir biçimiydi.

Önceki yazımızı hatırlarsak uygulama içerisindeki tüm yapıları kendimiz manuel olarak geliştirmiştik. Özellikle uygulama akışının değişmesini sağlayacak olan kısımlarda factory metotları kullanıp ilgili tiplerini çalışma zamanında otomatik olarak yaratılmasını sağlamıştık.

static IMessageSender CreateMessageSender()
{             
  string messageSender = ConfigurationManager.AppSettings["message"].ToString();             
   if (messageSender == "SMS")                 
       return new SMSSender();             
    return new MailSender();         
}         
static ILogger CreateLogger()         
{             
     string logger = ConfigurationManager.AppSettings["logger"].ToString();             
     if (logger == "DB")                 
         return new DBLogger();             
      return new FileLogger();         
}

 

Dependency Injection prensibini incelediğimizde bu prensip içerisindeki önemli noktalardan biri uygulama içerisindeki bileşenlerin birbirlerine sıkı sıkıya bağlı olmaması(loosely coupled) iken diğeri ise çalışma zamanında doğru tipin yaratılarak uygulama akışının sağlanması olduğunu görmekteyiz. Yukarıda gördüğümüz gibi factory metotlar kullanmak özellikle daha ufak projelerde işimizi görebilirken projeler büyüdükçe bu yöntem bizler için biraz sıkıntılı olabilmekte. Özellikle metotların uzaması, bakımının zorlaşması bizler için sorun çıkarmakta. İşte bu noktada Inversion of Control Containers (IoC Containers) dediğimiz kütüphaneler bizlerin yardımına koşmakta.

IoC Container’ların temel amaçlarını kısaca özetlememiz gerekirse, IoC Container’lar belirli konfigürasyonlara göre içerlerinde Dependency Injection sırasında uygulamanın akışını değiştirmek üzere kullanacağımız belirli Interface’leri implemente eden tipleri barındırırlar. Böylece bizler her Dependency Injection uyguladığımız bölümde yeniden factory metoda başvurup tip yaratma yerine doğrudan IoC Container’a başvuruda bulunuruz. Böylece IoC Container da içerisinde daha önceden bizim yaptığımız tanımlamayı kullanarak ilgili tipin üretilerek bize verilmesi görevini gerçekleştirir. Böylece yukarıda gördüğümüz gibi factory metotlar yazmamıza gerek kalmaz ve her yeni tip eklemesi olduğunda veya bir bölüme daha dependency Injection mekanizması kurmaya çalıştığımızda yeni bir factory metot yazmamıza gerek kalmaz.

Internette hemen kısa bir araştırma yaparsanız geliştirilmiş olan pek çok Ioc Container kütüphanesi görüyor olacaksınız. Microsoft Enterprise Library içerisinde bulunan Unity bloğu, Castle Windsor, Ninject projeleri benim hemen bir çırpıda sayabileceğim çözümler. Biz bu yazımızda Castle Windsor kütüphanesi üzerinden IoC container mekanizmasını inceliyor olacağız.

Castle Windsor

Castle Windsor kütüphanesini yaklaşık olarak 6 aydır kurumsal projelerimizde kullanmaktayız. Özellikle sağladığı basit IoC Container tanımlama yapısıyla ve yine IoC Container içerisinden tiplerin üretimi konusunda oldukça başarılı ve basit bir çözüm olduğunu sizlere söyleyebilirim.

Castle Windsor kütüphanesini kullanabilmek için öncellikle gerekli kütüphaneleri buradan indirmeniz gerekmekte. İndirdiğimiz zip klasörünü açarsak pek çok farklı runtime(Silverlight, .NET Framework) ve Framework (NET 3.5, .NET 4.0, .NET 4.0 Client Profiler) için geliştirilmiş olan kütüphaneleri görüyor olacağız.

WindsorLib

Şimdi ilk olarak bir .NET 4.0 Console Uygulaması yaratalım ve indirdiğimiz kütüphanelerden dotNET40 klasöründeki Castle.Core ve Castle.Windsor kütüphanelerini projemize referans olarak ekleyelim.

WindsorReferences

Projemizin temel yapısı önceki yazımızdaki gibi olacak. Bu nedenle o yazıda kullanmış olduğumuz interface’leri ve bu interface’leri implemente eden tipleri aşağıda bulabilirsiniz.

interface IMessageSender     
{         
   void SendMessage(string Message);
} 
interface ILogger     
{         
    void WriteLog(string message);
}
 
class DBLogger:ILogger
{         
    public void WriteLog(string message)
    {
         Console.WriteLine(String.Format("DBLogger : {0}", message));
    }
}
 
class MailSender:IMessageSender 
{
    public void SendMessage(string message)
    {
         Console.WriteLine(String.Format("MailSender : {0}", message));
    }
}
 
class FileLogger:ILogger 
{   
    public void WriteLog(string message)
    {
         Console.WriteLine(String.Format("FileLogger : {0}", message));
    }
}
 
class SMSSender:IMessageSender 
{
    public void SendMessage(string message)
    {
         Console.WriteLine(String.Format("SMSSender : {0}", message));
    }
}

 

Yine de kısaca uygulamanın nasıl çalıştığından bahsetmemiz gerekirse, uygulamamız temel olarak loglama ve mesaj gönderme işlerini gerçekleştirmekte. Ancak bir takım ihtiyaçlara göre örneğin loglama işlemi Database ve File olarak 2 farklı şekilde yapılabilirken yine mesaj gönderme işlemi de Mail ve SMS yoluyla yapılabilmektedir. Bu gibi değişen ihtiyaçları karşılamak için bir önceki yazımızda Dependency Injection kullanarak hem uygulamamız içerisindeki bağımlılıkları en aza indirmiş hem de uygulamanın daha genişletilebilir ve esnek olmasını sağlamıştık. Şimdi yapacağımız ise IoC Container kullanarak ilgili tiplerin üretimini IoC Container tarafına bırakmak.

Castle Windsor’ın yapısına baktığımızda Container mekanizmasını konfigürasyonunun 2 şekilde yapılabildiğini görmekteyiz.

Bunlar,

  • Fluent arayüz ile kod tarafından
  • Config dosyası kullanılarak

Kod Tarafından IoC Container’ın Konfigürasyonu

Castle Windsor’ın bizlere sağlamış olduğu IoC Container yapısını konfigüre etmek için kullanacağımız yollardan biri de kütüphanenin bize sağlamış olduğu fluent arayüzü kullanmak. Bunun için tüm IoC Container işlemlerini yönetmek için bir basit bir sınıf yazıyor olacağız. Bu sınıf temel olarak içerisinde IoC Container’ı yaratacak ve daha sonra da bizim parametre olarak vereceğimiz interface’I implemente eden ve konfigürasyon sırasında tanımlamasını yaptığımız tipi döndürüyor olacak. İlk olarak IoC Container’ı verilen konfigürasyonlara göre yaratan metodumuzu yazalım.

private static IWindsorContainer BootstrapContainer()         
{             
                return new WindsorContainer().Register(                     
                   Component.For<IMessageSender>().ImplementedBy<SMSSender>(),                     
                   Component.For<ILogger>().ImplementedBy<DBLogger>());
}

 

Yukarıdaki metoda baktığımızda aslında basit bir factory metot olduğunu görmekteyiz. Metodumuz IWindsorContainer interface’ini implemente eden bir IoC Container döndürmekte. Metodun içerisinde ise  IWindsorContainer interface’ini implemente eden WindsorContainer tipinden bir nesne yaratarak daha sonra bu tipin içerisinde bulunan Register metodunu kullanarak Dependency Injection sırasında kullanacağımız tiplerin konfigürasyonunu yapmaktayız. Windsor developerlara fluent bir arayüz sunduğu için aslında Register metodu içerisini okuyarak bile nasıl bir tanımlama yapıldığını anlamamız mümkün. Türkçe olarak metodun içerisinde yapılan tanımı özetlersek,

  • Ben senden IMessageSender interface'ini implemente eden tipi istediğimde bana bir SMSSender nesnesi ver.
  • Ben senden ILogger interface'ini implemente eden tipi istediğimde bana bir DBLogger nesnesi ver.

Gördüğünüz gibi aslında oldukça basit bir tanımlama yaparak hızlı bir şekilde IoC Container’ı yarattık. Şimdi ise yapmamız gereken ise ihtiyacımız olduğunda bize ilgili tipi döndürecek olan basit bir metot. Hemen bu metodu da yazalım ve ilerleyelim. :)

public static T Resolve<T>()         
{             
     return Container.Resolve<T>();         
}

Gördüğünüz gibi oldukça basit bir metot yazdık. Generic olarak geliştirdiğimiz bu metoda generic olarak IoC Container içerisinden almak istediğimiz tipin implemente ettiği interface’i veriyor olacağız. Daha sonra metot içerisinde ise Windsor’ın bize sağlamış olduğu Resolve metodu ile gereken tipi geri dönüyor olacağız.

Bir de son olarak tüm bağımlılık tanımlamalarımızı kod içerisinden yaptığımız için uygulama akışının çalışma zamanında değişmesi mümkün değil. Bu yüzden IoC Container’ımızı Singleton yapmamız doğru olacaktır. Singleton tanımlamamızı yaptıktan sonra tüm IoC Container işlemlerini yönettiğimiz IoCUtil isimli sınıfımızın tanımı şu şekilde olacaktır.

public static class IoCUtil     
{         
      private static IWindsorContainer _container = null;         
      private static IWindsorContainer Container          
      {              
           get             
           {                 
                if (_container == null)                 
                {                     
                       _container = BootstrapContainer();                 
                }                 
                return _container;             
           }         
      }         
      
      private static IWindsorContainer BootstrapContainer()         
      {             
           return new WindsorContainer().Register(                     
               Component.For<IMessageSender>().ImplementedBy<SMSSender>(),
               Component.For<ILogger>().ImplementedBy<DBLogger>());
      }         
      
      public static T Resolve<T>()         
      {             
           return Container.Resolve<T>();         
      }     
}

 

Şu ana kadar baktığımızda aslında IoC Container entegrasyonunun %90’lık kısmını bitirmiş bulunmaktayız. Şimdi son olarak geriye kalan kısım ise Dependency Injection uyguladığımız kısımlarda factory metotların kullanımını kaldırmak ve IoCUtil sınıfının Resolve metodu ile ihtiyacımız olan nesneleri almak.

class Processor     
{         
      ILogger logger = null;         
      IMessageSender messageSender;         
      
      public Processor()         
      {             
          logger = IoCUtil.Resolve<ILogger>();             
          messageSender =IoCUtil.Resolve<IMessageSender>();         
      }         
      public void Process()         
      {             
          logger.WriteLog("Log Text");             
          messageSender.SendMessage("Message Text");         
      }
}

Evet uygulamamız içerisinde IoC Container entagrasyonunu da tamamladık. Şimdi uygulamamızı çalıştıralım ve ürettiği çıktıya bir bakalım. ;)

IoCRun-1

Gördüğünüz gibi IoCUtil sınıfı içerisindeki BootstrapContainer metodu içerisinde yaptığımız tanımlamalara uygun çıktıyı elde ettik. Dilerseniz bu tanımlamaları değiştirerek uygulama akışının nasıl değiştiğini gözlemleyebilirsiniz.

Bölümün başında Windsor’ın Container tanımlaması yapmak için fluent arayüz dışında bir de config dosyası desteği olduğundan bahsetmiştim. Şimdi gelin bir de son olarak IoC Container içerisindeki tanımlamaları konfigürasyon dosyaları içerisinden nasıl yapabiliriz bu konuyu inceleyelim.

Config Dosyası İle IoC Container’ın Konfigürasyonu

Konfigürasyon tanımlaması kısmına baktığımızda aslında fluent interface’ten çokta fazla bir farkın olmadığını görüyoruz. İlk olarak config section tanımlamasını yaptıktan sonra component elementleri içerisindeki service ve type attributeleri ile ilgili IoC Container tanımlamasını yapabilmekteyiz. Service attribute’ü bizim ilgili interface’imizi belirtirken type attribute’ü ise IoC Container’a ilgili interface ile başvuruda bulunduktan sonra geri alacağımız tipi göstermekte. Bir de son olarak component elementinin unique bir id’si olması gerekmekte.

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <configSections>
    <section  name="castle" 
    type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, 
          Castle.Windsor" />
  </configSections>
  <castle>
    <components>
      <component id="logger"
     service="Windsor.Project.Interfaces.ILogger, Windsor.Project"
     type="Windsor.Project.Loggers.DBLogger, Windsor.Project"/>
 
      <component id="repository"
     service="Windsor.Project.Interfaces.IMessageSender, Windsor.Project"
     type="Windsor.Project.MessageSenders.SMSSender, Windsor.Project"/>
    </components>
  </castle>
</configuration>

 

Konfigürasyon dosyası içerisinde tanımlamalarımızı yaptıktan sonra son olarak IoC Container yaratma sırasında konfigürasyon dosyası içerisindeki castle elementinin içerisindeki tanımlamalarının kullanılmasını belirtmemiz için IoC Container yaratan BootstrapContainer isimli factory metodunda şu şekilde bir değişiklik yapmamız gerekmekte.

private static IWindsorContainer BootstrapContainer()         
{             
  return new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
}

 

Konfigürasyon dosyası kullanarak IoC Container tanımlamak için yapmamız gerekenler bunlar. Son olarak yine uygulamamızı çalıştıralım ve oluşan çıktıya bakalım.

IoCRun-2

Evet arkadaşlar bu yazımızda Dependency Injection pattern’I içerisinde işlerinizi oldukça kolaylaştıracak olan IoC Container yapısını Castle.Windsor kütüphanesi ile beraber inceledik. Sizlerde projelerinizde bu kütüphaneyi rahatlıkla kullanabilirsiniz. Tabi biz yazımız boyunca kütüphanenin çok küçük bir kısmını inceledik. Buradaki adresten çok daha detaylı bilgileri elde edebilirsiniz. Aşağıdaki linkten ise yazı boyunca işlediğimiz örnekleri indirebilirsiniz.

Umarım yazım sizler içn faydalı olmuştur.

Hoşçakalın,

Windsor.Project.rar (710,76 kb)

Tags:   ,
Categories:   Dependency Injection | IoC
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (4) | Yorumlar RSSRSS Yorum Takibi
Share

Entity Framework–ToList().Count vs Any() vs Count()

Cuma, 18 Kasım 2011 10:42 by ilkayilknur

Merhaba Arkadaşlar,

Bu yazımıza basit bir soru ile başlıyoruz :)

var email = from inc in entities.be_Settings                             
            where inc.SettingName == "email"                             
            select inc;

 

Yukarıdaki Linq-to-Entities sorgusunda kullandığımız kritere ait olan kaydın tabloda bulunup bulunmadığını nasıl anlarız  ve en sizin önerdiğiniz en doğru kullanım hangisidir ?

A) email.ToList().Count>0

B)email.Any()

C) email.Count()>0

.

.

.

.

.

.

.

.

.

.

.

Şimdi 3 şıkkı da sırayla inceleyelim ve doğru cevabı bulmaya çalışalım. (Eğer ben doğru cevabı zaten biliyorum diyorsanız yazıyı okumadan doğru cevap için yazının son paragrafına bakabilirsiniz ;) )

email.ToList().Count>0

İlk olarak A şıkkındaki kullanımı deneyeceğiz ve Linq sorgumuzu aşağıdaki gibi değiştiriyor olacağız.

var IsExist = (from inc in entities.be_Settings                                
               where inc.SettingName == "email"                                
               select inc).ToList().Count > 0;

 

Şimdi Linq sorgusu  çalıştıralım ve SQL Profiler ile Linq sorgusu tarafından üretilen SQL sorgusunu inceleyelim.

SELECT
[Extent1].[SettingName] AS [SettingName],
[Extent1].[SettingValue] AS [SettingValue]
FROM [dbo].[be_Settings] AS [Extent1]
WHERE N'email' = [Extent1].[SettingName]

Yazdığımız Linq Sorgusunun ürettiği SQL sorgusu yukarıdaki gibi. Sorgunun çalışması sonucunda aldığımız sonuç ise bizim istediğimiz sonuç. Yani bu yöntem pratikte çalışıyor. Şimdi geçelim B şıkkına :)

email.Any()

B şıkkı için ise Linq sorgumuzu şu şekilde değiştiriyoruz.

var IsExist = (from inc in entities.be_Settings                                
               where inc.SettingName == "email"                                
               select inc).Any();

 

Linq sorgusunun ürettiği SQL sorgusu ile aşağıdaki gibi oluyor.

SELECT
CASE WHEN ( EXISTS (SELECT
    1 AS [C1]
    FROM [dbo].[be_Settings] AS [Extent1]
    WHERE N'email' = [Extent1].[SettingName]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT
    1 AS [C1]
    FROM [dbo].[be_Settings] AS [Extent2]
    WHERE N'email' = [Extent2].[SettingName]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

B şıkkında da sorgumuz pratikte amacına ulaşıyor ve sonucu başarılı bir şekilde elde ediyor.

Şimdi gelelim son olarak C şıkkına :)

email.Count()>0

Son şıkkımız olan C şıkkı için de Linq sorgumuzu şu şekilde değiştiriyoruz.

var IsExist = (from inc in entities.be_Settings                                
               where inc.SettingName == "email"                                
               select inc).Count()>0;

 

Yukarıdaki Linq Sorgusunun ürettiği SQL Sorgusu ise aşağıdaki gibi oluyor.

SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
    COUNT(1) AS [A1]
    FROM [dbo].[be_Settings] AS [Extent1]
    WHERE N'email' = [Extent1].[SettingName]
)  AS [GroupBy1]

Baktığımız zaman yine pratikte de bu sorgumuz doğru cevabı bize döndürdü ve başarıyla ilgili kaydın tabloda bulunup bulunmadığı öğrenebildik.

Peki ya Doğru Cevap :)

Yazının buraya kadar olan kısmını okuduğunuzda tüm cevapların doğru olduğunu düşünüyor olabilirsiniz. Pratikte doğrudur ama en doğru kullanım sadece tek bir şıkta mevcut. Neden mi ? Gelin şu şekilde başlayalım.

Sizden sorudaki işlemi bir SQL sorgusu ile yapmanızı istesem sizler nasıl yapardınız ? (Tabi yine en doğru şekilde)

IF EXISTS(SELECT * FROM be_Settings bs WHERE bs.SettingName='email')
BEGIN
    --Gelecek işlemler
END

Eminim çoğunuz yukarıdaki SQL sorgusunu yazacaksınızdır. Nitekim bu kullanım en doğru olan kullanımdır performans açısından. Bu konuyla ilgili kısa bir araştırma yaparsanız EXISTS kullanımının hız açısından ne kadar iyi olduğunu kısa sürede bulacaksınızdır. İlgili makalelerden birine şuradan erişebilirsiniz.

Evet bu nedenden dolayı SQL tarafında bu yöntem en performanslı olduğundan yukararıdaki soru için bizim Linq sorgumuzdan da bu şekilde bir SQL üretmesini beklememiz oldukça doğaldır. Sanırım bu noktadan sonra doğru cevabı tahmin edebiliyorsunuzdur. Ancak gelin sırayla 3 yöntemi de kısaca inceleyelim.

A şıkkındaki kullanıma baktığımızda en maliyetli kullanım olduğunu söyleyebiliriz. Çünkü select sorgusu ToList metoduyla çalıştırılmakta ve sorgu sonuçları belleğe alınarak buradan List’in eleman sayısına bakarak kaydın bulunup bulunmadığına bakılmakta. Örneğin eğer ilgili kriterlere sahipkayıt sizin tablonuzda 1000 tane var ise bu bin kayıt öncelikle database’den getirilip nesnelere çevrilmekte ve daha sonra bir List’in içerisine atılmakta ve siz sadece bu List’in eleman sayısını kontrol etmektesiniz. Bu nedenle en maliyetli ve en kaçınmamız gereken kullanım A şıkkında.  !!!

B şıkkı ise bize doğru sonucu en performanslı bir biçimde getiren kullanım. Çünkü Any metodu kullandığımızda sorgu “EXISTS” kullanımına çevrilmekte ve en doğru SQL oluşturulmaktadır.

Son şıkkımızda da aslında yanlış olan noktayı tahmin edebiliyorsunuzdur. Count ifadesi Exists ifadesine göre yukarıda paylaştığım makaleyi baz alarak konuşursak yaklaşık 250 kez daha yavaş olduğu için doğru kullanım olmamakta.

Sonuç olarak baktığımızda aslında tüm kullanımlar bizlere doğru sonucu getirmekte. Ancak performans açısından baktığımızda tek bir cevap bulunmakta. O da B şıkkı. Yazılım geliştirirken belki de çoğu kez yanlış kullanımlar yüzünden performans kayıpları yaşayabiliyoruz. Önemli olan bu şekilde ufak kullanım detaylarını bilerek hem doğru kullanım alışkanlıkları kazanmak hem de performans açısından sorun yaşamayan uygulamalar geliştirmek. ;)

Umarım bu yazım sizler için faydalı olmuştur.

Görüşmek Üzere,

Categories:   Entity Framework
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (1) | Yorumlar RSSRSS Yorum Takibi
Share