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?
- async/await: I/O-yoğun (dosya, ağ, DB) beklemelerde UI’yi dondurmadan çalıştırır.
- Parallel/TPL: CPU-yoğun işleri çok çekirdeğe yayarak hızlandırır.
- Gereksiz yere
Task.Runile I/O işlerini thread havuzuna atmayın; gerçek kazanım CPU-yoğunda olur.
Performans İpuçları ve Dikkat Edilecekler
- Paylaşılan durumu azaltın; mümkünse yerel toplamalarla birleştirin.
- Gereksiz kilitlerden kaçının; gerekli olduğunda
InterlockedveyaConcurrentkoleksiyonlar kullanın. MaxDegreeOfParallelismile aşırı bağlam değiştirmeyi engelleyin.- UI uygulamalarında bloklayan çağrılardan (
.Wait()/.Result) kaçının.
Ö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,Concurrentkoleksiyonlar. - async/await ↔ Parallel: I/O-yoğun ↔ CPU-yoğun iş ayrımına göre seçin.
İlişkili Makaleler
C# Asenkron Programlama Temelleri (async/await)
C#’ta async ve await kullanımını öğrenin. Asenkron işlemler, Task yapısı ve performanslı uygulama akışları örneklerle anlatılıyor.
C# Asenkron Streamler (IAsyncEnumerable)
C#’ta IAsyncEnumerable ile asenkron stream kullanımını öğrenin. Veri akışını adım adım işleme senaryoları örneklerle anlatılıyor.
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.