Giriş

Asenkron programlama, .NET ekosisteminin hem modernliği hem de verimliliğinin temelidir. Ancak, “sadece async/await” ile sınırlı değil! Gerçek bir projede, işte karşımıza çıkan tüm kavramlar:

  • async / await
  • .Result
  • .Wait()
  • .GetAwaiter().GetResult()
  • ConfigureAwait(false)
  • async void
  • Task.Run()
  • Task.FromResult()
  • ValueTask

Her biri farklı amaca hizmet ediyor, farklı avantaj/dezavantaj getiriyor ve yanlış kullanıldığında uygulamanı “yavaş, kilitli veya çözülemez” hale getirebiliyor.

 

asenkron_programlama

Bu yazıda, her birini en ince detayına kadar ne işe yarar, nasıl kullanılır, hangi durumda kesinlikle kaçınılmalı, kod örnekleri, “pro-level” ipuçları, sık yapılan hatalar ve performans senaryoları ile açıklayacağım.

1. async / await: Temel Prensipler ve Derin Pratikler

1.1 async Nedir?

  • async anahtar kelimesi, bir metodun “asenkron” olacağını bildirir.
  • Bu metodun içinde “await” kullanılabilir.
  • Geriye çoğunlukla Task, Task<T>, ya da modern .NET’te ValueTask<T> döner.
  • Senkron metotlara async ekleyemezsin.

Kural:
Bir async metodun dönüş tipi

  • void (SADECE event handler’da!)
  • Task
  • Task<T>
  • ValueTask/ValueTask<T> olabilir.

Yanlış:

public async int Getir() // HATA! Dönüş tipi int olamaz.

Doğru:

public async Task<int> GetirAsync() { ... }

1.2 await Nedir?

  • “await”, bir Task’in bitmesini “thread bloklamadan” bekler.
  • await kullanırken metodun async olması gerekir.
  • await, arka planda işlem tamamlanana kadar mevcut thread’i başka işlerde kullanır (özellikle IO-bound işlemler için harika).

Basit Örnek:

public async Task<int> GetirAsync()
{
    await Task.Delay(1000);
    return 42;
}
  • Thread bloklanmaz: Kod, beklerken başka işlemler devam eder.
  • await olmayan bir async metot uyarı verir, ama derlenir: Sadece uyarı (“This async method lacks ‘await’ operators…”)

1.3 async/await’in Faydaları

  • UI Donmaz: WinForms/WPF/Mobile’da kullanıcı beklerken form donmaz.
  • Ölçeklenebilirlik: Sunucu uygulamalarında thread bloklanmadan aynı anda binlerce iş yapılabilir.
  • Kolay hata yönetimi: try/catch ile asenkron işlemler kolayca yönetilir.

2. .Result / .Wait(): Senkron Bekleme ve Tehlikeleri

2.1 .Result Nedir?

  • Bir Task’in sonucunu SENKRON olarak döndürür.
  • Thread bloklanır, yani uygulama durur ve “bitene kadar” hiçbir şey yapamaz.
  • UI veya ASP.NET context’te deadlock riski taşır.

Örnek:

Task<int> t = GetirAsync();
int val = t.Result;

2.2 .Wait() Nedir?

  • Task’in tamamlanmasını bekler, ama hiçbir değer döndürmez.
  • Senkron blokajı oluşturur.

Örnek:

Task t = BekleAsync();
t.Wait();

2.3 Tehlikeler ve Hatalı Kullanım Senaryoları

  • UI thread’de, .Result veya .Wait() kullanırsan uygulama kilitlenebilir!
  • ASP.NET senaryolarında, context çakışması ile deadlock yaşanabilir.

Deadlock Örneği (Gerçek Dünya):

public async Task<string> VeriGetirAsync() 
{
    await Task.Delay(1000); // Simülasyon
    return "tamam";
}

public string SenkronVeriGetir()
{
    return VeriGetirAsync().Result; // DEADLOCK riski!
}

Açıklama: Eğer metot bir context’i (örn. UI thread’i) capture ettiyse, .Result beklerken asla tamamlanamaz ve kilitlenir.

3. .GetAwaiter().GetResult(): Farklı Exception Handling ve İnce Farklar

  • .Result ile benzer şekilde senkron bekler.
  • FARKI: Exception handling. .Result ile AggregateException gelir; GetAwaiter().GetResult() ile orijinal exception doğrudan fırlatılır.

Örnek:

try
{
    var res = GetirAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
    // Doğrudan exception alınır.
}

Ne zaman tercih edilir?

  • Genellikle test veya migration amaçlı, ya da özel durumlarda “asıl exception” yakalamak için.
  • Yine de: Deadlock riski aynıdır!

4. ConfigureAwait(false): Context Yönetimi ve Performans

4.1 Temel Kullanım

  • await edilen task’in devamında, orijinal “synchronization context”e (örn. UI veya ASP.NET request context) DÖNÜLMEMESİNİ sağlar.
  • Performans ve context yönetimi için kritik!

Örnek:

public async Task VeriGetirAsync()
{
    await Task.Delay(500).ConfigureAwait(false);
    // Buradan sonra artık UI thread’inde değilsin!
}

4.2 Ne Zaman Kullanmalı?

  • Kütüphane, servis veya arka plan işlerinde (UI ile ilgisi olmayan kodda) her zaman önerilir.
  • UI uygulamalarında, await sonrası tekrar UI elemanlarına dokunacaksan kullanmamalısın.

4.3 Sık Yapılan Hatalar

  • UI uygulamasında: ConfigureAwait(false) sonrası, UI kontrollerine erişmeye kalkarsan, exception fırlatılır.

Yanlış:

await Task.Delay(500).ConfigureAwait(false);
label1.Text = "Bitti!"; // HATA: UI thread’inde değil!

5. async void: Neden Yalnızca Event Handler’da Kullanılmalı?

5.1 async void Nedir?

  • Bir metodu async olarak işaretler ama geriye Task dönmez.
  • Hatalar yukarıya “bubbling” yapmaz, yakalanamaz ve test edilemez.

Yanlış Kullanım:

public async void GenelKullanim()
{
    await Task.Delay(1000);
    throw new Exception("Hata!"); // try/catch ile yakalayamazsın!
}

Doğru Kullanım:

private async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(500);
}
  • Yani SADECE event handler’larda, zorunlu olduğunda async void kullanılır.

5.2 Neden Kaçınmalı?

  • Exception propagation (hata fırlatma) ve await edilebilme kaybolur.
  • Unit test yazılamaz, hata yönetimi zorlaşır.

6. Task.Run(): Arka Plan Thread ve CPU/IO Farkı

6.1 Task.Run() Nedir?

  • Bir işi arka planda, thread pool’da çalıştırır.
  • Aslında CPU-bound işler için idealdir; IO-bound işlemlerde gereksizdir.

Örnek:

public Task DosyaOkuVeİşleAsync(string yol)
{
    return Task.Run(() =>
    {
        var lines = File.ReadAllLines(yol);
        // Ağır işlem
    });
}

Doğru Kullanım: Uzun süren veya “CPU’yu yoran” işleri (ör: büyük resim işleme, şifreleme) asenkron yapmak.

Yanlış Kullanım: Zaten asenkron olan bir IO işlemini Task.Run ile sarmak: Yalnızca thread tüketir, hiçbir avantajı yoktur.

7. Task.FromResult(): Hazır Sonucu Asenkron Olarak Döndürmek

  • Bir değeri veya sonucu, asenkron metot imzası gereği Task olarak döndürmek için kullanılır.
  • Özellikle testlerde, “cache’de varsa beklemeden ver” gibi durumlarda idealdir.

Örnek:

public Task<string> GetirAsync()
{
    if (cache != null)
        return Task.FromResult(cache);
    return GetirDbAsync();
}

8. ValueTask ve ValueTask<T>: Hafif Asenkron Sonuçlar

8.1 ValueTask Nedir?

  • .NET Core 2.0+ ile gelen, küçük/çoğunlukla senkron dönen işlerde heap allocation’ı azaltır.
  • Eğer “sonuç çok hızlı ve genelde cache’den” geliyorsa, gereksiz Task objesi yaratmaz.

Örnek:

public ValueTask<int> GetirAsync()
{
    if (cache.HasValue)
        return new ValueTask<int>(cache.Value); // heap allocation yok!
    return new ValueTask<int>(GetirDbAsync());
}

8.2 Dikkat Edilmesi Gerekenler

  • ValueTask<T>, aynı anda birden fazla kez await edilemez (Task<T> ile uyumlu değildir).
  • Karmaşık senaryolarda Task<T> kullanmak daha güvenlidir.

9. Sıkça Sorulan Sorular (SSS) ve Pro-level İpuçları

async/await ile sync metodu çağırmak doğru mu?

  • Hayır. Sync metodu asenkron gibi göstermek sadece karmaşa yaratır. Gerçek asenkron IO/işlem yoksa, async/await kullanmak gereksizdir.

Her zaman ConfigureAwait(false) kullanmalı mıyım?

  • Hayır!
  • Kütüphane/servis yazıyorsan: Evet, performans için önerilir.
  • UI/web uygulaması yazıyorsan, await sonrası UI ile işin varsa kullanma.

async void unit test yazılır mı?

  • Yazılamaz. Sadece event’te zorunlu olduğunda kullan.

Task.Run ile IO işi arka plana atılır mı?

  • Hayır. Gereksiz thread kullanımı, ölçeklenebilirlik kaybı.

ValueTask ile performans kazanılır mı?

  • Çok nadir, sadece hot-path’de ve yüksek trafikli, genelde cache’den dönen işlerde.

.Result ve .Wait() ile Deadlock oluşur mu?

  • Evet! UI ve ASP.NET context’te ciddi kilitlenme riski.

Task.FromResult ve ValueTask ne zaman tercih edilir?

  • Elinde “hemen hazır” bir cevap varsa (cache gibi) ve metot async imzası gerektiriyorsa.

10. En Çok Yapılan Hatalar ve Gerçek Proje Tuzakları

  • .Result veya .Wait()’i production UI/web thread’de kullanmak
  • async void ile exception’ların yakalanmaması
  • Gereksiz yere Task.Run ile async işlemi sarmak (özellikle IO-bound)
  • ConfigureAwait(false) sonrası context kaybı ile UI’ya erişmeye çalışmak
  • ValueTask’ı yanlış await etmek (birden fazla await edilemez!)
  • Tüm async işlemleri aynı anda başlatmak, thread pool’u çökertmek

11. Kapsamlı Kod Örnekleriyle Karşılaştırma

1. Doğru async/await Kullanımı:

public async Task<string> VeriGetirAsync()
{
    await Task.Delay(1000);
    return "veri";
}

public async Task TestAsync()
{
    string v = await VeriGetirAsync();
    Console.WriteLine(v);
}

2. Deadlock’a Yol Açan Kötü Kullanım:

public string Senkron()
{
    return VeriGetirAsync().Result; // UI/Web thread’inde deadlock!
}

3. ValueTask ile Hafif Sonuç:

public ValueTask<int> GetirAsync()
{
    if (cache.HasValue)
        return new ValueTask<int>(cache.Value);
    return new ValueTask<int>(GetirDbAsync());
}

4. Task.Run ile CPU-bound ve IO-bound Karşılaştırması:

// DOĞRU: CPU-bound iş
await Task.Run(() => BüyükHesaplama());

// YANLIŞ: IO-bound iş
await Task.Run(() => HttpClient.GetStringAsync(url)); // Gereksiz

12. Özet ve En İyi Pratikler Tablosu

Yöntem Ne Zaman? Avantaj/Dezavantaj
async/await IO-bound, responsive UI, ölçeklenebilirlik Doğru thread kullanımı, kolay hata yönetimi
.Result/.Wait() SENKRON çağrı gerekirse (test/CLI) Deadlock riski, blokaj, kaçınmak gerek
.GetAwaiter().GetResult() Exception handling farkı için (özel durum) Deadlock yine var, çok nadir gerekli
ConfigureAwait(false) Kütüphane/serviste, context gerekmediğinde Performans, context kaybı riski
async void YALNIZCA event handler’da Hata yakalanmaz, test yazılamaz
Task.Run() CPU-bound işler Thread pool yönetimi
Task.FromResult() Hızlı cache/değer dönmek için Allocation yok, çok pratik
ValueTask Çok sık cache’den dönen, hızlı işler Allocation yok, karmaşıklık riski

13. Kaynaklar

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir