.NET Performans & Asenkron Programlama 4 dk okuma 18 okunma

Asenkron Programlamada Performans: I/O-bound vs. CPU-bound

Async/await, I/O-bound işlemlerde (veritabanı, HTTP, dosya) thread'i serbest bırakarak ölçeklenebilirliği artırır; ancak CPU-bound işlemlerde fayda sağlamaz, hatta ek yük getirir. Doğru kullanım, işlem türüne göre belirlenir: I/O için async/await, CPU yoğun işler için Task.Run veya paralel işleme. En sık hata, CPU-bound işi async metot içinde yapmak veya I/O'yu senkron bloke etmektir.

Asenkron Programlamada Performans: I/O-bound vs. CPU-bound

Asenkron Programlamada Performans: I/O-bound vs. CPU-bound

Async/await, .NET'te eşzamanlılık denince akla gelen ilk araçtır. Ancak yanlış kullanıldığında, performans kazancı sağlamak bir yana, ek yük ve gizli hatalar üretebilir. Bu yazının merkezinde tek bir soru var: İşlem gerçekten asenkron mu, yoksa CPU'yu meşgul eden bir iş mi? Cevap, doğru deseni belirler.


1. I/O-bound vs. CPU-bound: Temel Ayrım

Özellik I/O-bound CPU-bound
Ne yapar? Ağ, disk, veritabanı, HTTP çağrısı bekler Hesaplama yapar (matematik, şifreleme, görüntü işleme)
Darboğaz Gecikme (latency), bekleme süresi CPU çekirdekleri, işlem gücü
Async/await faydası ✅ Yüksek (thread serbest kalır) ❌ Yok (thread zaten meşgul)
Doğru araç async/await Task.Run, Parallel.For, PLINQ
Örnek await httpClient.GetAsync(url) await Task.Run(() => Hesapla())

Kritik Nokta: Async/await, bir işlemi daha hızlı yapmaz; yalnızca beklerken thread'i serbest bırakır. CPU-bound bir işte thread zaten çalışıyor olduğu için, async eklemek yalnızca state machine maliyeti ekler.


2. I/O-bound İşlemlerde Async/await: Gerçek Kazanç

Bir HTTP çağrısı veya veritabanı sorgusu düşünün. Senkron kodda, thread bu işlem tamamlanana kadar bloke olur; bu süre boyunca başka istekleri işleyemez. Async/await ile thread, I/O işlemi sürerken serbest kalır ve başka istekleri karşılayabilir.

Senkron (Bloke):

csharp

public Product GetProduct(int id)
{
    // Thread burada bekler; başka istek işleyemez
    var response = _httpClient.GetStringAsync($"api/products/{id}").Result;
    return JsonSerializer.Deserialize<Product>(response);
}

Asenkron (Serbest):

csharp

public async Task<Product> GetProductAsync(int id)
{
    // Thread I/O sürerken serbest kalır
    var response = await _httpClient.GetStringAsync($"api/products/{id}");
    return JsonSerializer.Deserialize<Product>(response);
}

Ölçeklenebilirlik Etkisi: 1.000 eşzamanlı istek geldiğinde, senkron yaklaşım 1.000 thread gerektirir (thread pool tükenir); asenkron yaklaşım ise I/O tamamlanana kadar thread'leri serbest bırakır, çok daha az thread ile aynı yük karşılanır.


3. CPU-bound İşlemlerde Async: Yanılgı ve Doğru Kullanım

CPU-bound bir işlemi async metot içinde yapmak, onu asenkron yapmaz. Yalnızca gereksiz bir state machine ve ek yük ekler.

Yanlış:

csharp

public async Task<int> HesaplaAsync(int x, int y)
{
    await Task.Delay(1); // Anlamsız; sadece async görünümü veriyor
    return x + y;         // CPU işi hala aynı thread'de
}

Doğru (CPU işini ayrı thread'e taşı):

csharp

public async Task<int> HesaplaAsync(int x, int y)
{
    return await Task.Run(() => AgirHesaplama(x, y));
}

Ancak Dikkat: Task.Run her CPU işi için doğru cevap değildir. ASP.NET Core gibi bir sunucuda, CPU-bound işi Task.Run ile thread pool'a atmak, thread pool'u tüketebilir ve ölçeklenebilirliği azaltabilir. Bu durumda doğru yaklaşım, işi ayrı bir arka plan servisine (BackgroundService, kuyruk) taşımaktır.


4. ConfigureAwait(false): Kütüphane Kodu İçin Kritik

await sonrası kodun, orijinal context'e (UI thread, ASP.NET request context) dönmesi gerekmediğinde ConfigureAwait(false) kullanmak, gereksiz context switch'ini önler.

csharp

// Kütüphane kodu: context'e dönmeye gerek yok
public async Task<string> FetchDataAsync()
{
    var response = await _httpClient.GetAsync(url).ConfigureAwait(false);
    return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}

Ne Zaman Kullanılmalı? Uygulama kodu değil, kütüphane kodu için. UI uygulamalarında, UI thread'ine dönmek gerekebilir; bu durumda ConfigureAwait(false) kullanılmamalıdır.


5. Sık Yapılan Hatalar

Hata Sonuç Çözüm
.Result veya .Wait() kullanmak Deadlock, thread bloke await kullanın
async void kullanmak Hata yakalanamaz, test edilemez async Task kullanın
CPU-bound işi async sanmak Ek yük, kazanç yok Task.Run veya arka plan servisi
I/O'yu senkron bloke etmek Thread pool tükenmesi Async API kullanın
Gereksiz Task.Run (ASP.NET'te) Thread pool baskısı İşi arka plana taşıyın

6. En İyi Pratikler

Pratik Açıklama
İşlem türünü belirleyin I/O mu, CPU mu? Doğru desen buna bağlıdır.
I/O'da async/await kullanın Veritabanı, HTTP, dosya işlemlerinde thread'i serbest bırakın.
CPU'da paralel işleme kullanın Parallel.For, PLINQ veya arka plan servisi.
Senkron API'leri bloke etmeyin .Result ve .Wait() deadlock riski taşır.
Kütüphane kodunda ConfigureAwait(false) Gereksiz context switch'ini önleyin.

Sonuç

Async/await, I/O-bound işlemlerde ölçeklenebilirliğin temel taşıdır; thread'i bekleme süresince serbest bırakarak çok daha az kaynakla çok daha fazla isteği karşılamanızı sağlar. Ancak CPU-bound işlemlerde async eklemek, kazanç sağlamaz; yalnızca ek yük getirir. Doğru yaklaşım, işlemin türünü doğru teşhis etmek ve uygun aracı seçmektir: I/O için async/await, CPU için paralel işleme veya arka plan servisi. Unutmayın: Async/await bir performans sihirbazı değil, bir ölçeklenebilirlik aracıdır; onu doğru yerde kullanmak, yanlış yerde kullanmaktan çok daha değerlidir.

Tüm yazılar Soru ya da yorumun mu var? Yaz