Menü

Neominal

BT Hizmetleri ve Danışmanlığı

28 Nisan 20262 dk okuma24 görüntüleme
Part 2: C# Paralel Programlama

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.Run kullanarak yeni bir thread başlatmayın. HttpClient veya Entity Framework gibi 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); 
    });
}