Sitemap
Press enter or click to view image in full size
Photo by Shavr IK on Unsplash

Tasklar, Threadler ve Yorgunluk

5 min readAug 17, 2025

--

Task, Async ve await sadece bir syntax keyword’u değil. Thread’ler de sanıldığı kadar karmaşık değil. Her ikisi de yanlış anlaşılıp, yanlış yerde kullanılırsa anlam verilemeyen tutarsızlıklar ve yorgunluktan başka bir şey getirmez.

Yazdığımız programlarda yapılan işler kimi zaman CPU-bound, kimi zaman ise I/O-bound’dur.

Yoğun hesaplama işlemleri, veri işleme, herhangi bir şeyi simüle etmek, bir veriyi sıkıştırmak veya bir veriyi başka bir türde veriye çevirmek gibi işler CPU-bound’dur.

Bir dosyaya sürekli log yazmak veya benzer dosya işlemleri, ağ ile iletişim kurup herhangi bir yerden bir şey istemek, veya herhangi bir yere bir şey göndermek, veritabanı işlemleri, kullanıcı etkileşimleri ise I/O-bound’dur.

Bellekte bir veri var, bu veriyi disk’e JSON olarak yazmak ve bir web servisine göndermek istiyoruz.

  • RAM’den veriyi edinmek — CPU-bound
  • Veriyi sadeleştirmek — CPU-bound
  • Veriyi JSON haline getirmek — CPU-bound
  • Veriyi diske yazmak — I/O-bound
  • Veriyi bir web servisine göndermek — I/O-bound

Burada görmemiz gereken şey, yazdığımız program disk, ağ gibi CPU’nun kendi mimarisi içerisinde işlem yapmadığı zaman bu I/O-bound’dur.

Yazdığımız programların RAM’e ulaşımı CPU mimarisinin doğal bir parçası ve görevidir, diske ulaşmak veya ağ üzerinden yapılan bir çağrı gibi düşünülmemelidir. Bundan dolayı RAM ulaşımlarını (istisnalar hariç) CPU-bound sayabiliriz.

Concurrency vs Parallelism

Concurrency (Eşzamanlılık)

İşler parça parça ilerler ve işlenir. Örnek olarak tek çekirdekli bir CPU’nuz varken, bir I/O işi yapıyorsunuz, bu işin bitmesini beklerken elinizdeki tek threadinizin başka bir işi halletmesi istenebilir. Fakat bir CPU-bound iş yapıyorsunuz ve bunun bitmesini beklerken birden fazla thread yoksa araya başka bir iş alamazsınız.

Kısaca Concurrency’de işler gerçekten aynı anda çalışmaz, beklemede olan bir thread’in kullanılması sağlanabilir.

Parallelism (Paralellik)

Aynı anda birden fazla işi gerçekten çalıştırmak ise paralelliktir. Bunun için birden fazla CPU veya birden fazla CPU çekirdeğine ihtiyacınız vardır. Örnek olarak tek CPU’nuz var ve bunun 8 çekirdeği var, aynı anda 8 tane CPU-bound işlem yapabilirsiniz.

Kısaca Parallelism’de işler gerçekten aynı anda çalışabilir, fakat donanım kısıtı vardır.

Concurrency kod yapısının ve kodun nasıl iş yapacağına dair optimizasyon ve aynı anda birden çok fazla işin ilerlemesini sağlayan çözümdür, Parallelism ise donanım seviyesine kadar inilmesi gereken ve işlerin aynı anda çalışmasını sağlayan çözümdür.

Task vs Thread

Task

ThreadPool üzerinde çalışan üst seviye bir soyutlamadır, thread değildir, bir işin sonucunu temsil eder. İşleri altta yine işletim sistemi thread’leri yürütür; Task ise bu işlerin soyut temsilidir. Task olarak yapılan bir iş I/O'dan cevap beklerken thread’e ihtiyaç duymaz ve herhangi bir Thread tüketmez.

Task’ın üretilme maliyeti düşüktür, bir programda çok sayıda çalışan Task’a sahip olmak normaldir. async await keyword’leri ile I/O bound işleri yönetmeyi kolaylaştırır. Yaşam döngüsü Task.Run, await, WhenAll gibi keyword’ler ile runtime’da yönetilebilir.

Thread

İşletim sistemi seviyesinde, alt seviye çalışan ve gerçek bir iş yapıcıdır. Thread üretme maliyeti yüksektir, bir programda ne kadar thread çalışabileceği programınızın çalıştığı ortamın donanımları ile sınırlıdır.

CPU-bound ağır işleri izole olarak ele almak, sabit olarak, sürekli çalışan işler için daha uygundur. Yaşam döngüsünü programcı yönetir, runtime’da ThreadPool buraya müdahale etmez, thread’in ne zaman başlayacağı ne durumlarda biteceği gibi konular kodlama esnasında programcı tarafından açıkça belirtilmelidir.

IO-Bound, Task ile await kullanımı, ThreadPool tarafından yönetilir. runtime bu Task’ların ne olacağına karar verebilir. await kullanana kadar bir thread tüketilmez.

var tasks = pageUrls.Select(url => http.GetStringAsync(url));
var pages = await Task.WhenAll(tasks);

Uzun süreli olarak bir thread’ı belirli bir iş için ayırmak, thread’ı oluşturmak, başlatmak ve bitirmek programcının sorumluluğundadır, runtime’da müdahale edilemez. Herşeyin önceden açıkça belirtilmiş olması gerekir.

var worker = new Thread(LongRunningCpuLoop) { IsBackground = true };
worker.Start();

Uygulama

Yukarıda örneklediğim CPU-bound ve I/O-bound işleri, producer → CPU workers → consumer olarak somutladığım bir program örneği yazalım:

  • Producer: Sensör verisini sürekli üretip inputQueue’ya koyar. Kısmen CPU-bound işlemler yaptığı için kendisine ait bir thread’de çalışır.
var producerThread = new Thread(() => ProduceLoop(inputQueue, cancellationTokenSource.Token))
{
IsBackground = true,
Name = "producer"
};
producerThread.Start();

static void ProduceLoop(BlockingCollection<SensorReading> input, CancellationToken cancellationToken)
{
Console.WriteLine($"Starting producer on thread {Environment.CurrentManagedThreadId}.");

var rnd = new Random();
var id = 0;
while (!cancellationToken.IsCancellationRequested)
{
try
{
var values = new double[128];
for (var i = 0; i < values.Length; i++)
values[i] = rnd.NextDouble() * 2000 - 1000;

var reading = new SensorReading(Id: ++id, Values: values, CapturedAt: DateTime.UtcNow);

// Add to the input queue
if (!input.IsAddingCompleted)
input.Add(reading, cancellationToken);

// Simulate some delay
cancellationToken.WaitHandle.WaitOne(5);
}
catch (OperationCanceledException)
{
break;
}
catch (ObjectDisposedException)
{
break;
}
}
}
  • CPU Workers: inputQueue’dan aldıkları verileri sadeleştirip JSON’a çevirir, CPU-bound olduğu için kendilerine ait thread’lerde çalışır. Sonucu outputQueue’ya gönderir.
var cpuThreadCount = Math.Max(1, Environment.ProcessorCount - 1);
var cpuWorkerThreads = new Thread[cpuThreadCount];
for (var i = 0; i < cpuWorkerThreads.Length; i++)
{
cpuWorkerThreads[i] = new Thread(() => CpuWorkerLoop(inputQueue, outputQueue))
{
IsBackground = true,
Name = $"cpu-worker-{i}"
};
cpuWorkerThreads[i].Start();
}

static void CpuWorkerLoop(BlockingCollection<SensorReading> input, BlockingCollection<string> output)
{
Console.WriteLine($"Starting CPU worker on thread {Environment.CurrentManagedThreadId} for CPU-BOUND processing");

var jsonOptions = new JsonSerializerOptions { WriteIndented = false };

// Cancellation token not used here, as we want to process all items in the input queue.
foreach (var item in input.GetConsumingEnumerable(CancellationToken.None))
{
// CPU-BOUND simplification + conversion to JSON
var simplified = Simplify(item);
var json = JsonSerializer.Serialize(simplified, jsonOptions);

if (!output.IsAddingCompleted)
output.Add(json, CancellationToken.None);
}

return;

static SensorReading Simplify(SensorReading input)
{
var trimmed = new double[input.Values.Length];
for (var i = 0; i < trimmed.Length; i++)
{
var v = input.Values[i];

if (v < -1e6)
v = -1e6;

if (v > 1e6)
v = 1e6;

trimmed[i] = v / 1000.0;
}

return input with { Values = trimmed };
}
}
  • Consumer: outputQueue’dan gelen JSON’ları diske yazar ve web servisine yollar. Bu aşamada I/O-bound olduğundan async/await ve Task ile yönetilir, main thread’de çalışır, işlemler veya consumer loop’u kendisine ait özel bir thread’e ihtiyaç duymaz. Belki Consumer loop’un kendisi spesifik bir thread alabilirdi ama içerideki I/O işlemleri yine o thread’i kullanabilirdi.
var consumerTask = Task.Run(() => ConsumerLoop(outputQueue));

static async Task ConsumerLoop(BlockingCollection<string> output)
{
Console.WriteLine("Starting sensor data consumer");
var dir = Path.Combine(AppContext.BaseDirectory, "out");
Directory.CreateDirectory(dir);

const string endpoint = "https://example.org/api/readings";
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);

// Cancellation token not used here, as we want to process all items in the output queue.
foreach (var json in output.GetConsumingEnumerable(CancellationToken.None))
{
var fileName = Path.Combine(dir, $"{DateTime.UtcNow:yyyyMMdd_HHmmss_ffff}.json");

// Start two I/O-bound tasks
var write = WriteJsonToDiskAsync(fileName, json, CancellationToken.None);
var post = PostJsonAsync(httpClient, endpoint, json, CancellationToken.None);

// Wait for both tasks to complete
await Task.WhenAll(write, post);
}
}

Sonuç?

--

--

No responses yet