Blog

Asenkron Sucuklu Yumurta Yapmak

11.12.2020

Her ne kadar yemek tarifi verecek gibi gözüksek de; bugün sizlere asenkron (Asynchronous) programlama ne demektir, nerelerde asenkron programlama yapmak mantıklıdır, bunları anlatmaya çalışacağım. Benim gibi üniversitede bilgisayar mühendisliği eğitimi almayan ya da almış olsa da bu tarz kavramları anlamakta zorlanmış arkadaşlara, konuyu gerçek hayattan bir örnek ile ve en basit haliyle (kodlama dahil) anlatmaya çalışacağım.

***

Bir bilgisayar programı çalıştığı süre içerisinde temelde iki tip işlem yapar. Bunlardan biri CPU-bound, diğeri ise IO-bound işlemlerdir.

CPU-bound işlemler adından anlaşılacağı üzere, asıl işi CPU’ nun gerçekleştirdiği, CPU hızımız arttıkça işlemin yapılma süresinin kısalacağı işlemler olarak adlandırılır. İki sayıyı toplamak ya da π(pi) sayısının 1000. rakamını hesaplamak bu tarz işlemlere örnek olarak verilebilir.

IO-bound işlemler ise, CPU-bound işlemlerin tersine, asıl işin CPU dışında gerçekleştiği (CPU’nun işi yapması için harici bir şeyi göreve çağırması) işlemler olarak adlandırabilir. Diskten verinin okunması, bir uç noktadan (endpoint) http(s) üzerinden bir veri çekilmesi ya da veri tabanından bir veri okunması IO-bound işlemlere örnek olarak verilebilir.

Şimdi gelelim, asenkron programın bu işin neresinde olduğuna... Öncelikle asenkron programlamayı asla ve asla multi-thread programlama ile karıştırmamaya dikkat edelim. Asenkron programlamada asıl amacımız, programımızın çalışmasının devamlılığının engellendiği noktaları azaltmak iken; multi-thread programlamadaki asıl amacımız ise aynı anda birden fazla thread’e yaptırabilmektir. Asenkron programlamada öncelikli amacımız, akışımızı bloklayan özellikle IO-bound işlemlerin sonuçlarının gerçekten ihtiyaç duyuldukları an gelene kadar beklenilmemesini sağlamak ve dolayısıyla asıl akışımızın bloklanmasını en düşük seviyede tutabilmektir.

Şimdi gerçek hayattan bir örnek ile asenkron programlama nedir, bunu anlamaya çalışalım:

Sabah kalktık ve kahvaltıda sucuklu yumurta yemek istiyoruz. Bir programa sucuklu yumurta yaptırmak istesek, kabaca aşağıdaki gibi bir algoritma ile yaptırabiliriz. Bu algoritma ile direkt yapması biraz peanut butter jelly time videosuna dönebilir tabii ki, fakat kabaca bu algoritma yumurtamızı yememize ve olayı anlamamıza yeterli olacaktır.

*"Senkron program tarif ve süreleri*

Buradaki işleri sırası(senkron) ile yaptığımızda sucuklu yumurtanın yapılması ve masamızın hazırlanması tam 445 sn (7 dk 25 sn) sürmektedir.

*Senkron programımızdaki akış (tüm kodlar github’da mevcut)*

Örnek metot görünümü aşağıdaki gibidir. Thread.Sleep() metotu ilgili thread’ i parametre olarak verilen süre (örnekte 45 bin milisanitedir) kadar bekleten bir metottur. Bekletilen bu süre içerisinde işin yapıldığını varsayıyoruz :)

*"Masa Hazırla" metodumuzun görünümü*

Senkron bir akışa sahip programımızın akışı ve çıktısı ise aşağıdaki şekildedir:

*Senkron program akışı*

Bu süre bizim için makul bir süre iken, her zaman daha iyisi vardır diyerek bu süreci daha kısa hale getirmeye çalışalım. Çıktıdan da görüldüğü üzere, programda 4 yerde bekleme mevcut ve buralarda aslında temelde hiç bir iş (sürenin geçmesini beklemek dışında) yaptırmıyoruz. Peki daha öncesinde yaptığımız bazı işleri buralara (her zaman süre olarak araya sığmak zorunda olmasa da) sıkıştıramaz mıyız ? Mesela yumurtaları kırma, çırpma ve tuz ekleme işlemini tavanın ısınmasını beklerken yapsak? Ya da masa hazırlama işlemimizi sucukları yumurtalarımızın tavada pişmesini beklerken yapsak? Normalde de böyle yapıyoruz zaten değil mi? İşte bütün mesele zaten, gerçek hayatta çoğu zaman kolaylıkla yapabildiğimiz asenkron işlemlerimizi programımıza da yaptırabilmek. Haydi bu dediklerimizi uygulayalım ve sonucu görelim.

*Asenkron Tarif ve Süreleri*

Burada iki işin bitmesini beklemek yerine o aralıkta asıl yapmamız gereken işi yaparak tam 90 saniye kazandık ve artık bu süreç bizim için yalnızca 355 sn (5 dk 55 sn) alıyor.

Biraz teknik konuşmak gerekirse, bu akıştaki asenkron olarak yaptığımız işlemleri tavanın ısınmasını beklemek ve yumurtanın pişmesini beklemek olarak düşünebiliriz.

Bunun için ben .net-.net core camiasında yer alan ve asenkron işler yapabilmemizi oldukça kolay hale getiren; async, await ve task üçlüsünden yararlanacağım. async kelimesi asenkron yapılacak bir metodun tanım kısmına (imza) gelerek ilgili metodun asenkron olacağını belirtirken, await kelimesi de ilgili asenkron metot içerisinde yer alan bir işlemin yapılmaya başlandığını fakat uzun sürebileceğini, kendisini çağıran bölümün akışını devam ettirebileceğini, ancak kendisini çağıran yerin bu metodun yaptığı işlemin sonucuna ihtiyaç duyması durumunda normalde yaptığı işlemi bloklaması gerektiğini belirten yer (satır) gibi düşünebiliriz. Biraz karmaşık geldiğinin farkındayım fakat bu yazıdaki asıl amaç, asenkron programlama mantığını anlatmak olduğu için bu 3'lüye dair konuyu daha fazla irdelemeyi düşünmüyorum.

Metotlarımızın görünümü aşağıdaki gibi olacaktır:

*Asenkron programımızdaki akış (tüm kodlar github’da mevcut)*

Asenkron metotlarımız aşağıdaki gibidir. Asenkron çalışan metotlara isim verirken metot isminin sonuna async ifadesini eklememiz ilgili metodu kullanacak diğer yazılımcılar için bir ipucu olacak ve bu isimlendirme vasıtasıyla metodun asenkron olduğu bilgisini elde edeceklerdir. Koymazsak derleme hatası alınmayacaktır elbette, iyi bir isimlendirme yöntemidir.

*TavaBekleAsync metodu*

İlk metodu açıklamak gerekirse, metot; kendisi çağrıldığında Console’a bir yazı yazar ve daha sonrasında “Tavanın ısınmasını bekleme işi (thread’ i 60 sn beklettiğimiz yer) bende, beni çağıran yer, sen devam et” der. Bu söyleme işlemini await satırında yapar. Bu işi bitince de console’a tava ısındı yazdırır. Metodun task dönüş tipine sahip olmasının sebebi, kendisini çağıran yerin tavanın ısınma sürecinin bitip bitmediğini bilmeye ihtiyacı olması. Aksi takdirde tavanın ısınıp ısınmadığını bilmediğimizden "KabiHazirla()" metodu biter bitmez "SucuklariDiz()" metoduna geçeceğiz ve tavanın ısınması "KabiHazirla()" metodundan daha uzun süreceğinden, henüz ısınmamış bir tavaya sucukları atmış olacağız.

*PismesiniBekleAsync metodu*

Tüm bunların yanısıra, aynı şekilde yukarıdaki akışta da görüleceği üzere; "PismesiniBekle()" satırı çağırılacak ve console’a "Pişmesi bekleniyor" yazılacak. Metodun tamamlanması beklenmeden kendisi çağıran yere, bir task döndürerek beklemeye geçildiğinde kendisini çağıran yerdeki akışta bir sonraki satır çalıştırılacak ve masa hazırlanmaya başlanacak. Eğer pişmenin tamamlanmasını beklemezsek (Task.WaitAll()" ile içine yazdığımız task-tasklar’ın bitmesini beklemesi gerektiğini söyledik) "MasaHazirla()" bittikten sonra bir sonraki satır olan “Afiyet Olsun” kısmı çalıştırılır, pişmemiş bir yumurtayı yemeye çalışırdık. Sonra da yeterince beklersek, tam pişmemiş yumurtayı yerken console’a yazılan yumurta pişti yazısını görebiliriz :)

Nasıl ki gerçek hayatta yumurtalarımızın pişmesini beklerken ve bu arada masayı hazırlama işlemine geçmişken, masayı hazırlama işlemimiz bitse bile "Afiyet olsun" diyebilmemiz için pişirme işleminin bitmesini bekliyorsak; yazdığımız koddaki akışta da aynı şekilde "Afiyet olsun" diyebilmemiz için "Pişirme metodunun bitmesini bekle" demeliyiz.

Asenkron çalışan programımızın akışı ve çıktısı ise aşağıdaki şekildedir:

*Asenkron program akışı*

Görüldüğü üzere tavanın ısınması beklenirken ve sucuklu yumurtanın pişmesi beklenirken, başka işler yapılmış; sucuklu yumurta hazırlama süremizde kısalma olmuştur.

***

Asenkron programlama mantığı, özümsemesi biraz zor olsa da; gerçek hayat ile ne kadar bağdaştırabilirsek kavraması bir o kadar kolay hale gelebilecek bir durumdur. Multi-thread programlamadan farkı ise (teknik çok detaya girmeden) sucuklu yumurtayı eşinizle beraber yapmaya çalıştığınızı (işleri yapabilen bir kişinin daha olduğunu) ya da sucukları doğrarken aynı anda yumurtaları kırıp, çırpmaya başladığınızı düşünebilirsiniz :) Burada task kütüphanesi yeni bir thread açar-açmaz konusuna girmiyorum tabii ki. Dediğim gibi bununla ilgili detaylı anlatım istenirse ayrı bir yazıyı buna ayırabilirim.

Kabaca asenkron programlamayı bu şekilde özetleyebiliriz. Artık IO-bound işlemlerde asıl akışı bloklamadan işler yapabileceğimizi öğrendik. Naçizane önerim; sizler de mutlaka gündelik hayatta amatör-profesyonel kodlamalarda, asenkron programlama mantığını hatırlayın ve uygulamaya çalışın. Umarım faydalı bir içerik olmuştur. Buraya kadar okuyan özellikle genç arkadaşlarımıza teşekkür ediyorum. Kendinize iyi bakın, faydalı işlerde görüşmek üzere...

NOT: Kulllanılan kodları görmek isteyenler için (çok basit olsalar da) github bağlantısını paylaşıyorum.

Fatih Aydın - İşNet Yazılım Uzmanı