C# Async/Await: En Tehlikeli 5 Tuzak
Async/Await, C# dilinin en sevilen özelliklerinden biridir ancak "kullanması kolay" olması, "doğru kullanmayı" garanti etmez. İşte profesyonel projelerde en sık karşılaşılan 5 ölümcül tuzak ve çözümleri:
Tuzak 1: Senkronizasyon Bağlamı Nedeniyle Deadlock (Kilitlenme)
-
Sorun: UI (WPF/WinForms) veya ASP.NET (Eski) uygulamalarında bir
SynchronizationContextvardır. Eğer birasyncmetodu.Resultveya.Wait()ile senkron olarak çağırırsanız, threadTasktamamlanana kadar bloke olur. AncakTasktamamlandığında devam etmek için yakalanan context'e (UI thread) ihtiyaç duyar. UI thread zaten bloke olduğu için karşılıklı bekleme (Deadlock) oluşur. -
Çözüm: Mümkünse "Async all the way" (yukarıdan aşağıya asenkron) prensibine uyun. Zorunlu kalınırsa
.ConfigureAwait(false)kullanın ama en iyisi asla.Resultveya.Wait()kullanmayın; bunun yerineawaitkullanın.
Tuzak 2: ConfigureAwait(false)'ı İhmal Etmek (Performans ve Kilit Riski)
-
Sorun: Varsayılan davranışta
await'den sonraki kod, orijinal context'te (UI veya Request thread) çalışmak ister. Bu, gereksiz bir iş parçacığı geçişine (Thread Switch) neden olur ve performansı düşürür. Ayrıca yukarıdaki Deadlock riskini artırır. -
Çözüm: UI ile etkileşime girmeyen (örneğin alttaki katmanlar, veritabanı veya HTTP çağrıları) tüm
await'lerde.ConfigureAwait(false)kullanın.csharp
await httpClient.GetStringAsync(url).ConfigureAwait(false);
Tuzak 3: async void Kullanmak (En Büyük Günah)
-
Sorun:
async voidmetotlar içinde fırlatılan hatalar, çağıran tarafından yakalanamaz (try-catch çalışmaz) ve doğrudan uygulamayı çöktürür (Application Crash). Ayrıca geri dönüş tipi olmadığı için ne zaman bittiğini takip edemezsiniz. -
Çözüm: Sadece UI event handler'ları (Button_Click) için
async voidkullanın. Bunun dışındaki TÜM durumlardaTaskveyaTask<T>döndürün.
Tuzak 4: "Fire and Forget" (Ateşle ve Unut) Fiyaskosu
-
Sorun: Arka planda çalışan bir işlemi beklememek için bazen
_ = MyMethodAsync();yazılır. Ancak bu metodun içinde bir hata oluşursa, bu hata sessizce yutulur (Silent Failure). Log kaydı bile kalmaz ve uygulama hatalı duruma geçer. -
Çözüm: Eğer gerçekten fire-and-forget yapacaksanız, mutlaka bir exception handling mekanizması ekleyin.
csharp
_ = Task.Run(async () => { try { await MyMethodAsync(); } catch (Exception ex) { Logger.Log(ex); } });(Not: .NET 6+ arka plan hizmetleri için
BackgroundServiceveyaIHostedServicedaha güvenlidir.)
Tuzak 5: using ve Async/Zamanlama Karmaşası
-
Sorun: Bir
HttpClientveyaDbContextnesnesiniusingbloğu içinde yakalayıp içindeawaityaptığınızda, metodun içinde işlem devam ederkenDisposeçağrılabilir. Bu durum, "ObjectDisposedException" fırlatılmasına yol açar. -
Çözüm:
using'in kapsamını (scope) doğru ayarlayın. Eğer nesneyi metot dışına taşıyacaksanız,usingkullanmayın, üst seviye (örn. constructor'dan alınan) bir bağımlılık olarak yönetin (Dependency Injection ile).
Ekstra Bonus Tuzak - ValueTask Performansı:
Çok sık çağrılan ve genelde senkron sonuç döndüren metotlar için Task yerine ValueTask kullanmak heap tahsisini (alloc) azaltır. Ancak ValueTask'i iki kere await yapmayın veya .Result ile çağırmayın, aksi takdirde hata alırsınız.
Sonuç: Async/Await güçlüdür ama kuralları vardır. "Async all the way", "ConfigureAwait(false) bilinci" ve "asla async void" kurallarını hayat felsefesi haline getirirseniz, uygulamalarınız hem hızlı hem de kararlı çalışacaktır.