Menü

Neominal

BT Hizmetleri ve Danışmanlığı

29 Nisan 20262 dk okuma12 görüntüleme
Part 4-C# Üretici-Tüketici Deseni

Bir geliştirici için en büyük kabuslardan biri, uygulamanın bellek (RAM) kullanımının kontrolsüzce şişmesidir. Eğer büyük veri setleriyle çalışıyorsanız, verinin tamamının gelmesini beklemek yerine, gelen her parçayı anında işlemeye başlamalısınız.

1. IAsyncEnumerable

C# 8.0 ile hayatımıza giren IAsyncEnumerable<T>, asenkron bir foreach yapısı kurmamızı sağlar. Bu, veritabanından milyonlarca satır çekerken veya devasa bir dosyayı satır satır okurken RAM'i korumanın en zarif yoludur.

2. Üretici-Tüketici (Producer-Consumer) Deseni

Bazen veriyi okuma hızı (I/O) ile işleme hızı (CPU) birbirini tutmaz. Biri hızlıyken diğeri yavaş kalabilir. System.Threading.Channels kütüphanesi, bu iki dünya arasında yüksek performanslı, thread-safe bir tampon bölge (buffer) oluşturur.

Aşağıdaki kod bloğu hem Parallel hem de Async davranışı birleştirmemizi sağlar.


using System.Threading.Channels;

public async Task ProcessDataWithChannels()
{
    // 1000 öğelik bir kanal (buffer) oluşturuyoruz.
    var channel = Channel.CreateBounded<string>(1000);

    // PRODUCER: Veriyi asenkron okuyan ve kanala yazan görev.
    var producer = Task.Run(async () =>
    {
        await foreach (var line in ReadLargeFileAsync("bigdata.log"))
        {
            await channel.Writer.WriteAsync(line);
        }
        channel.Writer.Complete();
    });

    // CONSUMER: Kanaldan gelen veriyi PARALEL işleyen görev.
    // MaxDegreeOfParallelism ile CPU çekirdeklerini kullanıyoruz.
    var consumer = Task.Run(async () =>
    {
        var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
        
        await Parallel.ForEachAsync(channel.Reader.ReadAllAsync(), options, async (line, ct) =>
        {
            // CPU-Bound işleme
            AnalyzeLine(line);
            await Task.Yield(); // Thread Pool dengesi için küçük bir mola
        });
    });

    await Task.WhenAll(producer, consumer);
}

Neden Bu Yapıyı Kullanmalıyız?

  1. Bellek Yönetimi: BoundedChannel sayesinde, tüketici yetişemezse üreticiyi yavaşlatırız (Backpressure). Böylece RAM patlamaz.

  2. Maksimum Verim: Dosya hala diskten okunurken (I/O), ilk okunan satırlar çoktan paralel olarak işlenmeye (CPU) başlar. İki aşama birbirini beklemez.

  3. Thread Güvenliği: lock veya karmaşık ConcurrentQueue yönetimleriyle uğraşmazsınız; Channels altyapısı bunu sizin yerinize en optimize şekilde yapar.