Ne Bu Asenkron Programlama ?
Çalıştırdığımız kodun nasıl yazıldığı kadar nasıl çalıştığı da hem geliştiriciler hem de kullanıcılar için büyük bir önem taşır. Yazımı kötü olan bir kodun okunması, çalışması ve üzerinde geliştirilme yapılması oldukça zahmetlidir. Kodumuzu clean code prensipleriyle yazsak bile sistemin çalışma sırasındaki işleyişine hakim değilsek bu bize kötü yazılmış kod kadar maaliyet yaratabilir.
Dosya işlemleri, veritabanlarıyla konuşmak veya ağ üzerinden harici yapılara bağlanmak saniyeler alabilir ve hatta fark bile edemeyeceğimiz hızlarda yapılan temel aritmetik işlemler uç uca eklendikçe artık can sıkıcı bir seviyeye gelebilir. Bilgisayarımızın kodları nasıl işlettiğine, zaman gibi bir maliyeti nasıl kısaltabileceğimize ve “Biliyor musun kodu bir refactor ettim, 2 saniyede çalışan kod 300 milisaniyede takır takır çalıştı.” gibi lafları nasıl kurabileceğimize gelin birlikte bakalım.
Programlama Dillerinin İşlemleri Çalıştırma Mantığı Nasıldır?
Propramlama dilleri, derleyiciler veya Node.js gibi çalıştırma ortamları kodumuzda bulunan satırları verdiğimiz parametrelere göre yahut dilin kendi yapısına göre ne zaman bir sonraki satıra geçeceğine farklı farklı karar verebilirler.
Bu işleyişi temel olarak şu şekilde iki bölüme ayırabiliriz:
Synchronous / Blocking
Arka arkaya birden fazla fonksiyon çağırdığımızda bu işleyiş stilinde her fonksiyon çalıştırılır ardından fonksiyondan cevap gelene kadar kodun bir sonraki adıma geçmesi engellenir.
Synchronous(senkron) ve Blocking(bloklamalı) yapılarının mantıkları aynı olsa da kullanıldığı yerler farklıdır.
Network gibi işlerde synchronous kelimesi kullanılırken dosya okuma örneğini verebileceğimiz IO gibi işlemlerinde ise blocking kelimesi kullanılır.
Asynchronous / Non-Blocking / Event Based
Bu yapıda ise fonksiyonlarımız çağrılır ve cevap gelmesi beklenmeden bir sonraki adıma geçilir. Genellikle birbiriyle mantıksal ilişkisi olmayan görevleri yaparken birinin başlaması için öbürünün bitmesinin gerekmediği durumlarda kullanılır.
Event based(olay bazlı) yapıda ise daha önceden belirlenmiş olaylar tetiklenmeden çalıştırma yapılmaz. Burada olayların hangi sırayla tanımlandığı değil hangi sırayla tetiklendiği önemlidir. HTML ve JavaScript’teki event’ler bunun en güzel örneğidir. Bu terim de yeni başlayanlar için soru işaretli olduğu için buna da değinmeyi uygun gördüm.
async/await Kullanımı
C# 5.0, C++20, Python 3.5, F#, Hack, Julia, Dart, Kotlin 1.1, Rust 1.39, Nim 0.9.4, JavaScript ES2017, Swift 5.5, Zig ve bazı eklentilerle birlikte deneysel olarak Scala asenkron porgramlama için uygun koşulları sağlar. Dilden dile bu anahtar kelimelerin yazımı farklılık gösterebilir.
Bir fonksiyonu asenkron işletebilmek için async
ile bunu bildirmeliyiz. Tip güvenli dillerde fonksiyonumuzun dönüş tipini Task
veya Promise
olarak belirterek dönüşün uzun sürebileceğini belirtip içine generic olarak asıl dönüş tipini yazmalıyız.
async Task<int> getUserId(string username){
int id;
// Mantıksal işlemler
return id;
}
“Senkron programlama tamam ama asenkron proramlama yaparken cevap gelmeden nasıl işime devam edeceğim ki?” sorusunu kendinize sorduysanız gelin bir de await
anahtar kelimesine bakalım.
await
ile asenkron işleyişi kırıp cevap gelene kadar işlemi beklenmesi gerektiğini belirtiyoruz.
async Task<int> getUserId(string username){
User user = await findUserFromDatabase(username);
return user.id;
}
Örnek Senaryo
Büyük bir Excell dosyasınında bulunan malzeme bilgilerini okuyacak, her satır için PDF olarak fatura oluşturacak ardından bunları bir kullanıcıya mail atacak ve excell’deki bilgileri veritabanına kaydedecek bir sistemimiz olduğunu düşünelim.
Bunu yapacak senkron kod basitçe şöyle olurdu:
Fonsiyonların sırasıyla 1, 2, 2 ve 1 birim zamanda çalıştığını varsayarsak şöyle bir zaman grafik elde ederiz.
Koddaki excellOku
ve pdfOlusturVeGetir
dışındaki fonksiyonlar birbirinden bağımsızdır. Bundan dolayı onları ayrık olarak çalıştırmayı deneyebiliriz.
faturaBilgilendirmeMailiGonder
ve veritabaninaKaydet
fonksiyonları çalıştırılmaya başlanacak ve hemen bir sonraki adıma geçilecek. Buna göre zaman çizelgesi şöyle olurdu:
Çizelgeden de anlaşılabildiği üzere son iki işi neredeyse aynı anda başlatarak zamandan tasarruf etmemiz mümkün oldu.
Sonuç
Düzgün kurgulanmış bir asenkron kodla çalışmak bize gözle görülebilir bir zaman kazancı sağlayacaktır. Unutulmamalıdır ki birden fazla yapıyla(fonksiyon, koşul, döngü) ilişki içinde olan verilerimizi korumak için özellikle de ona atama yapılacağı zaman await
veya mutex gibi yapılarla o anda sadece bir işlemin onunla ilgilenmesini sağlamalıyız çünkü cpu’daki iş yükü, ağ yoğunluğu gibi belirleyemeyeceğimiz nedenlerden dolayı işlemlerimiz beklediğimizden farklı sürede çalışabilir ve bu da kodda yazdığımız sırayla atama yapılmasına engel olur.