Yükleniyor...

C# Task Parallel Library (TPL) ve Paralel Programlama

C#’ta Task Parallel Library ve paralel programlamayı öğrenin. Task, Parallel ve eşzamanlı işlem senaryoları örneklerle anlatılıyor.

.NET’te Task Parallel Library (TPL), çok çekirdekli işlemcilerden faydalanarak işleri paralel çalıştırmayı kolaylaştırır. TPL ile Task, Parallel, Concurrent koleksiyonlar ve PLINQ gibi araçlar kullanılarak CPU-yoğun işleri hızlandırabilirsiniz. Bu makalede temel kavramlar, doğru kullanım alanları, iptal/istisna yönetimi ve pratik örnekler yer alır.


TPL Nedir? Task ile Başlamak

Task, bir işi (görevi) temsil eder. Task.Run CPU-yoğun işleri thread havuzuna atar. Birden fazla işi aynı anda başlatıp Task.WhenAll ile bekleyebilirsiniz.


using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Task t1 = Task.Run(() => Islem("A", 1000));
        Task t2 = Task.Run(() => Islem("B", 800));
        Task t3 = Task.Run(() => Islem("C", 1200));

        await Task.WhenAll(t1, t2, t3);
        Console.WriteLine("Tüm işler bitti.");
    }

    static void Islem(string ad, int ms)
    {
        Console.WriteLine($"{ad} başladı");
        Task.Delay(ms).Wait(); // örnek amaçlı bekleme (CPU-yoğun değil)
        Console.WriteLine($"{ad} bitti");
    }
}

Not: Gerçek I/O beklemeleri için async/await tercih edilir; Task.Run CPU-yoğun işler içindir.


Parallel.For / Parallel.ForEach

Parallel sınıfı, döngüleri otomatik olarak parçalayıp iş parçacıklarına dağıtır. Paylaşılan değişkenlere erişirken thread-safe teknikler (ör. Interlocked) kullanın.


using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        int toplam = 0;

        Parallel.For(0, 1_000_000, i =>
        {
            // Yarış durumu (race condition) olmaması için Interlocked
            Interlocked.Add(ref toplam, 1);
        });

        Console.WriteLine($"Toplam: {toplam}");
    }
}

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        var sayilar = new List<int> { 1, 2, 3, 4, 5 };
        Parallel.ForEach(sayilar, n =>
        {
            Console.WriteLine($"{n} işlendi (Thread: {Environment.CurrentManagedThreadId})");
        });
    }
}

ParallelOptions: Eşzamanlılık Düzeyi ve İptal

MaxDegreeOfParallelism ile paralellik derecesini sınırlayabilir; CancellationToken ile iptal desteği verebilirsiniz.


using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        var cts = new CancellationTokenSource();
        var po = new ParallelOptions
        {
            MaxDegreeOfParallelism = Environment.ProcessorCount - 1, // çekirdek sayısına göre
            CancellationToken = cts.Token
        };

        // 2 sn sonra iptal
        Task.Run(async () => { await Task.Delay(2000); cts.Cancel(); });

        try
        {
            Parallel.For(0, 1000, po, i =>
            {
                po.CancellationToken.ThrowIfCancellationRequested();
                IsCpuBound(i); // CPU-yoğun iş
            });
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("İşlemler iptal edildi.");
        }
    }

    static void IsCpuBound(int i)
    {
        // örnek iş
        double x = 0;
        for (int k = 0; k < 50_000; k++) x += Math.Sqrt(k + i);
    }
}

Yerel Toplayıcılarla (Local Init/Finally) Ölçekli Toplama

Paylaşılan bir değişkeni kilitlemek yerine her iş parçası için yerel toplayıcı kullanıp sonunda birleştirin.


using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        long globalToplam = 0;

        Parallel.For<long>(0, 10_000_000,
            () => 0, // her thread için yerel toplam
            (i, loop, yerelToplam) =>
            {
                yerelToplam += i % 10;
                return yerelToplam;
            },
            yerelToplam => { System.Threading.Interlocked.Add(ref globalToplam, yerelToplam); });

        Console.WriteLine($"Toplam: {globalToplam}");
    }
}

Concurrent Koleksiyonlar

Paralel üretim/tüketim senaryolarında ConcurrentBag<T>, ConcurrentQueue<T>, ConcurrentDictionary<TKey,TValue> gibi koleksiyonlar kilitsiz/az kilitli erişim sunar.


using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        var bag = new ConcurrentBag<int>();

        Parallel.For(0, 1000, i => bag.Add(i));

        int sayac = 0;
        while (bag.TryTake(out _)) sayac++;

        Console.WriteLine($"Çekilen eleman: {sayac}");
    }
}

PLINQ (Parallel LINQ)

Büyük koleksiyonlar üzerinde sorguları paralel çalıştırmak için AsParallel() kullanılır. Sıraya duyarlılık gerekiyorsa AsOrdered() ekleyin; aksi halde performans için sırasız çalışır.


using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var dizi = Enumerable.Range(1, 1_000_000).ToArray();

        var sonuc = dizi
            .AsParallel()
            .Where(x => x % 3 == 0)
            .Select(x => x * x)
            .Take(10)
            .ToArray();

        Console.WriteLine(string.Join(", ", sonuc));
    }
}

İstisna Yönetimi

Parallel çağrılarında birden fazla istisna fırlatılabilir; AggregateException ile yakalanır. Task tabanlı akışlarda da benzer şekilde await sırasında istisnalar yüzeye çıkar.


using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        try
        {
            Parallel.Invoke(
                () => throw new InvalidOperationException("A"),
                () => throw new ApplicationException("B")
            );
        }
        catch (AggregateException ex)
        {
            foreach (var e in ex.InnerExceptions)
                Console.WriteLine($"Hata: {e.GetType().Name} - {e.Message}");
        }
    }
}

async/await mi, Parallel mi?


Performans İpuçları ve Dikkat Edilecekler


Örnek: Görüntü İşleme İşini Paralelleştirme

Aşağıdaki örnek, bir dizindeki resim dosyalarını paralel olarak işliyormuş gibi davranır (simülasyon). Gerçek senaryoda burada CPU-yoğun filtreleme/thumbnail üretimi yapılabilir.


using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        string klasor = "C:\\\\Resimler";
        var dosyalar = Directory.Exists(klasor)
            ? Directory.GetFiles(klasor, "*.jpg")
            : Array.Empty<string>();

        var po = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
        Parallel.ForEach(dosyalar, po, dosya =>
        {
            // CPU-yoğun filtre uygulama simülasyonu
            var ad = Path.GetFileName(dosya);
            Console.WriteLine($"İşleniyor: {ad}");
            Thread.SpinWait(3000000); // Simülasyon
            Console.WriteLine($"Bitti: {ad}");
        });

        Console.WriteLine("Tüm dosyalar işlendi.");
    }
}

TL;DR

  • TPL, paralel çalışmayı kolaylaştırır: Task, Parallel, PLINQ.
  • Parallel.For/ForEach ile döngüleri çekirdeklere yayabilirsiniz.
  • MaxDegreeOfParallelism ve CancellationToken kısıtlama/iptal sağlar.
  • Thread-safety şart: Interlocked, yerel toplayıcılar, Concurrent koleksiyonlar.
  • async/await ↔ Parallel: I/O-yoğun ↔ CPU-yoğun iş ayrımına göre seçin.

İlişkili Makaleler

C# Process ve Thread Yönetimi

C#’ta process ve thread yönetimini öğrenin. Çok iş parçacıklı yapı, süreç kontrolü ve sistem kaynaklarıyla çalışma örneklerle.