C# ASYNC AWAIT NEDİR?

İki paralel async timeline şeridi, await süspansiyon noktalarında mor noktalar ve eğri devam okları, mor tonlarda

Bir Windows Forms uygulaması açıyorsunuz. Kullanıcı "Raporu indir" butonuna basıyor. Ağ üzerinden 40 saniyelik bir indirme başlıyor — ve pencere donuyor. Başlık çubuğunda "Yanıt vermiyor" yazısı beliriyor. Kullanıcı sabırsızlanır, uygulamayı kapatır, müşteri desteği aranır. Aynı sahne ASP.NET tarafında daha sessiz ama daha pahalıdır: bir veritabanı sorgusu bekleyen istek thread'i tutar, 200 eşzamanlı istekte thread havuzu tükenir, sunucu yavaşlamaya başlar. async/await bu iki sorunun da temel cevabıdır. C# 5.0 ile dile eklenen bu iki anahtar kelime, asenkron kodu yazma şeklini fonksiyon zincirleri ve callback'lerden senkron görünümlü, sıralı okunan bir akışa dönüştürdü.

Async ve Await Hangi Sorunu Çözer

Senkron bir metod çağrıldığında thread işi bitirene kadar bloklanır. CPU yoğun bir hesaplama yapıyorsa bu mantıklıdır — thread gerçekten çalışır. Ama I/O işlemlerinde — disk okuma, ağ isteği, veritabanı sorgusu — thread büyük çoğunlukla işletim sisteminin yanıtını bekler. Bu beklemede CPU boştadır ama thread bağlıdır. Asenkron model bu bekleme süresinde thread'i serbest bırakır, başka iş yapmasına izin verir, sonuç döndüğünde işin devamını başka (veya aynı) bir thread'e bağlatır.

UI tarafında hikaye biraz farklıdır. UI thread'i sadece bir tanedir ve pencerenin tüm çizim ve etkileşim olayları onun üstünde çalışır. Uzun bir iş UI thread'ini meşgul ederse arayüz tepkisiz kalır. async/await burada UI thread'ini I/O bekleme süresince boşa çıkararak donmayı engeller.

Task ve Task<T> Nedir

Async modelin temel taşı Task sınıfıdır. Bir Task, "gelecekte tamamlanacak bir iş" temsilidir. Task sonuç döndürmeyen bir iş için, Task<T> ise T tipinde bir sonuç döndüren iş için kullanılır.

Task'ın bir yaşam döngüsü vardır: Created, WaitingForActivation, Running, RanToCompletion, Faulted, Canceled. Geliştirici genelde bu durumları doğrudan yönetmez — async/await sözdizimi arka planda yönetir. Önemli olan zihinsel model: bir Task, henüz tamamlanmamış olabilecek bir vaattir.

JavaScript'ten gelenler için kısa bir köprü: Task büyük ölçüde Promise ile aynı işi yapar. await da JavaScript'teki await ile aynı sezgiyi taşır. Ama implementasyon detayları farklıdır — Task daha zengin bir API'ya sahip, scheduler ile etkileşir, iptal token'larını destekler.

async ve await Anahtar Kelimeleri Pratikte

Bir metodun başına async yazdığınızda derleyiciye "bu metod içinde await kullanacağım" sinyali verirsiniz. Dönüş tipi Task, Task<T> veya void (sadece event handler için) olmalıdır.

await bir Task'ı bekler. Karşısındaki iş tamamlanana kadar metodun geri kalanını "pause" eder; thread serbest kalır. İş bittiğinde metod kaldığı yerden devam eder.

Klasik bir HTTP isteği örneği:

  • public async Task<string> HaberlerGetirAsync() — metod async işaretli, Task<string> döner
  • using var client = new HttpClient(); — istemci oluştur
  • var response = await client.GetAsync("https://api.example.com/news"); — istek tamamlanana kadar bekle, thread serbest
  • return await response.Content.ReadAsStringAsync(); — yanıt gövdesini oku ve döndür

Aynı kodu await olmadan yazsanız callback zinciri veya ContinueWith kullanmanız gerekirdi — iç içe geçmiş, okunması zor bir yapı. async/await bu yapıyı senkron görünümlü tek bir akışa indirir. Asenkron programlama tekniklerini ve .NET ekosisteminde örüntülerini bir bütün olarak öğrenmek için C# için pratik eğitim hem temel sözdizimini hem ileri konuları sırayla işler. Resmi derinleme dokümantasyon için Microsoft Learn C# kaynakları referans olarak güvenilir tutulur.

Task yaşam döngüsü akışı: Created WaitingForActivation Running RanToCompletion durumları ve Faulted Canceled terminal durumlar oklarla bağlı

ConfigureAwait ve Senkronizasyon Bağlamı

await kullandığınızda Task tamamlandıktan sonra metodun geri kalanı varsayılan olarak aynı senkronizasyon bağlamında devam eder. UI uygulamasında bu mantıklıdır — await'ten sonra UI elemanlarını güncelleyeceksiniz, UI thread'e dönmek gerekir. Sunucu tarafında ise bu davranış gereksiz bağlam değişimine yol açar.

await someTask.ConfigureAwait(false); şeklinde yazarsanız derleyiciye "tamamlandıktan sonra orijinal bağlama dönmen gerekmiyor" demiş olursunuz. Kütüphane kodu yazıyorsanız bu pratik fiilen zorunludur — kütüphaneniz UI uygulamasından da web uygulamasından da çağrılabilir, bağlama yapışmak deadlock üretebilir.

Yaygın Hatalar ve Yanlış Anlamalar

async/await görünüşte sade ama detayda incelikli bir konudur. Sık karşılaşılan tuzaklar:

  • async void kullanmak: Event handler dışında async void yasak kabul edilmelidir. Exception fırlatıldığında yakalanamaz, uygulamayı çökertir. Bunun yerine async Task kullanın.
  • .Result veya .Wait() çağırmak: Asenkron bir Task üzerinde senkron olarak beklemek deadlock'a yol açabilir; özellikle UI ve klasik ASP.NET bağlamında. "async all the way" kuralı: bir noktada async başladıysa zincirin tamamı async olmalı.
  • Gereksiz async sarmalama: İçinde tek satır await olan ve await sonucu döndüren metodu async yapmak gereksiz state machine üretir. Sadece return SomeTaskAsync(); şeklinde Task'ı doğrudan döndürmek daha verimlidir (ama exception fırlatma davranışı değişir, dikkat).
  • Loop içinde sıralı await: 10 bağımsız HTTP isteğini for döngüsünde await ile çağırırsanız sırayla çalışırlar. Paralel istemek istiyorsanız Task listesi toplayıp Task.WhenAll(tasks) ile bekleyin.
  • CPU yoğun işi async sananlar: async I/O için tasarlanmıştır. CPU yoğun bir hesaplama için thread'i serbest tutmaz — sadece başka bir thread'e devreder. Bunun için Task.Run ayrı bir araçtır.

Bu hataların büyük kısmı, async/await'i "sihirli bir hız artırıcı" gibi düşünmekten kaynaklanır. Asenkron model bir hız değil ölçek aracıdır — sunucu daha fazla eşzamanlı isteği aynı thread sayısıyla çevirir.

Performans ve Ne Zaman Kullanmamalı

Her metodu async yapmanın bir maliyeti vardır. Derleyici async bir metod için state machine üretir; bu küçük bir bellek ve CPU yüküdür. Saniyede milyonlarca kez çağrılan, mikrosaniye seviyesinde performans hassasiyeti olan iç döngü kodlarında bu maliyet hissedilir.

Pratik kural: gerçek bir I/O işlemi varsa async kullanın. Sadece senkron hesaplama yapan bir metodu async yapmak hem gereksiz overhead hem yanıltıcı API sözleşmesi yaratır. CPU yoğun bir işi UI thread'inden uzaklaştırmak istiyorsanız Task.Run doğru araçtır; ama bunu sunucu kodunda kullanmak çoğu zaman yanlıştır çünkü thread havuzundan thread çekersiniz — zaten paralel çalışan isteklerin kullandığı havuzdan.

async/await modern .NET geliştirmenin merkezindedir. Entity Framework Core'dan ASP.NET Core minimal API'ya kadar tüm modern API'lar asenkron tasarlanmıştır. Doğru anlaşıldığında hem kullanıcı deneyimi hem sunucu ölçeği için en etkili tek araçtır; yanlış kullanıldığında ise zor teşhis edilen deadlock'ların ve performans kayıplarının kaynağı olur.