Asenkron programlamaya dair bilinen en büyük yanılgı, her uzun süren işe async/await ile yapmaya çalışmaktır. Oysa kodun neden beklediğini analiz etmeden doğru mimariyi kuramazsınız. Bekleme sebebiniz donanım seviyesinde nerede dar boğaz oluşturuyor ? Disk/Network mü, yoksa İşlemci mi?
1. I/O-Bound İşlemler:
Veritabanı sorguları, API çağrıları veya dosya sistemi işlemleri I/O-bound'dur. Burada işlemci aslında etkin rol oynamaz. Veri networken akarken ya da veriyi diske yazarken thread'i o noktada bekletmek, modern mimaride kabul edilemez bir lükstür.
Bu tarz senaryolarda Async/Await tercih ederiz.
Buradaki amaç işi hızlandırmak değil, ölçeklenebilirliği (scalability) artırmaktır. await kullandığınızda, o işi yapan thread havuzuna (Thread Pool) geri döner ve başka bir HTTP isteğini karşılamaya gider. Veri geldiğinde ise herhangi bir müsait thread işi kaldığı yerden devralır.
Altın Kural: I/O işlemlerinde
Task.Runkullanarak yeni bir thread başlatmayın.HttpClientveyaEntity Frameworkgibi kütüphanelerin sunduğu doğal asenkron metodları kullanın.
2. CPU-Bound İşlemler:
Eğer elinizde milyonlarca satırlık bir veriyi işlemek, karmaşık bir görüntü algoritması çalıştırmak veya devasa bir JSON verisini parse etmek gibi işler varsa, artık I/O değil CPU-bound dünyasındasınız demektir. Burada asenkron yapı (async/await) size tek başına bir şey kazandırmaz; çünkü işlemcinin yapması gereken bir "iş" vardır ve o iş bitmeden sonuç gelmez.
Bu senaryoda Parallel Programming (TPL) tercih ederiz.
Parallel.ForEach, Parallel.For veya PLINQ kullanarak işi parçalara böler ve işlemcinin tüm çekirdeklerini (Core) aynı anda kullanıma sokarız.
Neden Async Değil? Çünkü asenkron programlama işi eşzamanlı (concurrency) yönetir, Parallel programlama ise işi aynı anda (parallelism) yapar.
10 tane I/O işlemini tek bir thread ile asenkron olarak yönetebilirsiniz fakat 10 tane ağır hesaplamayı hızlandırmak için 10 ayrı çekirdeğe ihtiyaç duyabiliriz.
public async Task ProcessLogsAsync(List<string> filePaths)
{
// I/O Bound İşlem (Dosyaları asenkron okuma)
// Task.WhenAll ile tüm dosyaları thread bloklamadan, paralel tetikleyerek okuyoruz.
var readTasks = filePaths.Select(path => File.ReadAllTextAsync(path));
string[] allFileData = await Task.WhenAll(readTasks);
// CPU Bound İşlem (Veriyi paralel işleme)
// Artık veri RAM'de. Şimdi işlemci çekirdeklerini aktif olara kullanma zamanı.
Parallel.ForEach(allFileData, (fileContent) =>
{
// Ağır analiz algoritması burada çalışıyor
AnalyzeComplexJson(fileContent);
});
}