C#’ta await kullanmak sadece bir bekleme demek değildir. Derleyici metodu arka planda yeniden yazıyor ve dönüş tipi Task oluyor. Ne demek istediğimi daha iyi anlamak için örnekler üzerinden anlatacağım.
Diyelim ki elimizde bir ürün listesi var ve ID’sine göre bir ürün almak istiyoruz:
public async Task<Product> GetProductAsync(int id)
{
var product = await GetProduct(id);
return product;
}Sorumuz burada: return product yazdık ama metot dönüş tipi Task. Neden?
Gördüğüm açıklamaların çoğu bir kural gibi aynı şeyi söylemiş, “eğer metodunuzda await kullanıyorsanız, dönüş tipi otomatik olarak bir Task içine sarılır.” diyorlar. Neden böyle çalıştığına dair ekstra bilgi vermiyorlar. Bu yüzeysel bir kural gibi geliyor. Oysa bu bir dil zorunluluğu değil, teknik bir gereklilik.

Leaky Abstraction
Bir soyutlama (abstraction), altında yatan karmaşıklığı gizlemelidir. Ama eğer o karmaşıklığın bazı parçaları dışarı sızarsa yani kullanıcı olarak o karmaşayı fark edersek buna leaky abstraction denir.
C# bağlamında neden “leaky” diyoruz?
async / await yapısı asenkron kodun karmaşasını gizlemek için tasarlanmıştır.
Product product = await GetProductAsync(5);Bu kodu yazdığınızda kodumuz senkron gibi görünür. Ama aslında bu kodun altında Task zinciri ve state machine öğeleri çalışır. Biz bu detayları görmeyiz, dolayısıyla soyutlama budur.
Size güzel bir örnek gösterelim:
// Senkron versiyon:
public Product GetProduct(int id)
{
var products = GetProducts();
return products.FirstOrDefault(p => p.Id == id);
}
// Asenkron versiyon:
public async Task<Product> GetProductAsync(int id)
{
var products = await GetProductsAsync();
return products.FirstOrDefault(p => p.Id == id);
}Bunların ikisi de aynı işlevi görmüyor mu kardeşim neden birisi Task<T> dönüyor?
Bunu daha iyi anlayabilmek için biraz inceleyelim ve bu sebeple soyutlamayı bir kenara bırakıp, bu kodu doğrudan “Task” kullanarak inceleyelim.
Eğer async kullanmasaydık ve aynı mantığı manuel yazsaydık:
public Task<Product> GetProductTaskAsync(int id)
{
return GetProductsAsync().ContinueWith(task =>
{
List<Product> products = task.Result;
Product product = products.FirstOrDefault(p => p.Id == id);
return product;
});
}Bu kod bloğunda return olarak “Task<Product>” dönecektir.
ContinueWith ile “await sonrası kod”u bir görev (task) olarak sarıyoruz. Bu aslında derleyicinin async ile otomatik olarak yaptığı işin elle yazılmış halidir.
await arkasında asenkron işin tamamlanmasını bekleyen bir yapı barındırıyor ve derleyici bunu otomatik olarak bir devam işlevine (continuation) dönüştürüyor.
Şimdi bir sorunumuz var: “GetProductTaskAsync” metodundan “product” değerini döndürmek istiyoruz, ancak bu değer Task’ın devam (continuation) bloğu içinde döndürülüyor. Bunu nasıl dışarı çıkarabiliriz?
Görünüşe göre “ContinueWith” bir değer döndürüyor. Yukarıda kullandığımız aynı tekniği kullanarak bunun ne olduğunu bulalım.
await Kullanımı
Şimdi await yazmadan metodumuz Task dönecek şekilde manuel olarak ilerletelim.
public Task<Product> GetProductTaskAsync(int id)
{
Task<List<Product>> productsTask = GetProductsAsync();
Task<Product> result = productsTask.ContinueWith(task =>
{
List<Product> products = task.Result;
Product pr = products.FirstOrDefault(p => p.Id == id);
return pr;
});
return result;
}Artık metot sadece bir Task döndürüyor ve istediğimiz şey bu. Eğer biri bu metottan dönen değeri await ederse, bir Product nesnesi elde eder.
Burada GetProductsAsync() çağrısını başlatıyoruz, tamamlandığında (ContinueWith) devam işlemini elle tanımlıyorsun ve sonra Task türünde yeni bir görev oluşturuş onu döndürüyorsun. Biraz uğraştırıcı değil mi?
Bu yaklaşım daha karmaşık, daha hataya açık ve daha az okunabilir Ama arka planda await’in yaptığı şey aslında tam olarak bu. Basit bir keyword ama önemli olan işlevi tabi.
İşte buna “sızıntı” diyoruz. Buradaki sızıntı elbette kötü değil. Metodun dönüş tipini Task yapmak, await kullanımının sağladığı büyük kolaylık yanında devede kulak kalır. await kullandığında metot Task dönecek ama bu sayede karmaşık callback zincirleriyle uğraşmak zorunda kalmayacaksınız.
await bize asenkronluğu kolay gösterir ama tamamen gizleyemez. Bu kolaylığın bedeli ise metot artık Task döndürmek zorundadır.
Bu kodu daha okunabilir kılmaz, aynı zamanda Task yapısının tüm karmaşık işini senin yerine yapar.
Makalenin başlığını “await güzel bir soyutlama ama biraz sızdırıyor” olarak da belirtebiliriz.



Teşekkürler