Berk Akademi
Birebir ders başvurusu Ücretsiz ön görüşme Ana Sayfa

C# async void ve async Task: Hata Yakalama Rehberi

csharp-async-void-async-task-hata-yakalama-rehberi
Bu yazıda neler var?
  1. async void ve async Task arasındaki temel fark nedir?
  2. Hangi async metotlar await edilebilir?
  3. try/catch neden async void çağrısındaki hatayı yakalayamaz?
  4. İki kısa C# örneğiyle bekleme ve hata akışını görün
  5. async void ne zaman meşru bir tercihtir?
  6. Birim testlerde async Task seçimi ve teşhis şeması
  7. Sık Sorulan Sorular

C# async void ile async Task arasındaki temel fark, çağıran kodun işlemi izleyip izleyememesidir. async Task, işlemin tamamlanmasını await ile beklemeye ve oluşan istisnaları çağrı zincirinde yönetmeye imkân verir. async void ise çağıran koda tamamlanmayı temsil eden bir değer döndürmez.

Bu nedenle bir asenkron metodu değerlendirirken yalnızca async anahtar sözcüğüne bakmak yeterli değildir. Metodun imzasını, nasıl çağrıldığını, beklenip beklenemediğini, istisnaların nereye aktığını ve nasıl test edileceğini birlikte incelemek gerekir.

async void ve async Task arasındaki temel fark nedir?

async Task biçimindeki bir metot, devam eden işlemi temsil eden bir Task nesnesi döndürür. Çağıran kod bu nesneyi await ederek işlemin bitmesini bekleyebilir:

async Task VerileriKaydetAsync()
{
    await DosyayaYazAsync();
}

await VerileriKaydetAsync();
Console.WriteLine("Kayıt tamamlandı.");

Son satırdaki mesaj, asenkron işlem tamamlandıktan sonra çalışır. DosyayaYazAsync sırasında bir istisna oluşursa bu hata döndürülen Task üzerinden izlenebilir ve çağıran tarafta uygun bir try/catch bloğuyla yönetilebilir.

async void ise işlemi temsil eden bir nesne döndürmez:

async void VerileriKaydet()
{
    await DosyayaYazAsync();
}

VerileriKaydet();
Console.WriteLine("Metot çağrıldı.");

Burada mesajın yazılması, kayıt işleminin tamamlandığını göstermez. Çağıran kodun elinde bekleyebileceği bir Task bulunmadığı için tamamlanma anını doğrudan takip etmek, sonucu çağrı zincirine katmak ve hatayı aynı akışta yönetmek zorlaşır.

async ve await kullanmak, kodun otomatik olarak paralel çalışacağı veya belirli bir iş parçacığında yürütüleceği anlamına gelmez. Bu sözdizimi öncelikle beklemeler içeren işlemlerin akışını düzenler. Doğru zihinsel model şu beş soruya dayanır: Metodun imzası nedir, nasıl çağrılır, çağıran kod onu bekleyebilir mi, istisna nasıl taşınır ve davranış nasıl test edilir?

Hangi async metotlar await edilebilir?

Hangi async metotlar await edilebilir?

Bir metodun async olması, o metoda yapılan çağrının mutlaka await edilebileceği anlamına gelmez. Çağıran kod açısından belirleyici olan dönüş tipidir. Yaygın üç imza şöyle karşılaştırılabilir:

Dönüş tipi Çağıran kodun bekleyebilmesi İstisna akışının yönetimi Tipik kullanım alanı
Task await ile beklenebilir Döndürülen görev üzerinden çağıran koda taşınabilir Sonuç üretmeyen genel asenkron işlemler
Task<T> await ile beklenebilir Döndürülen görev üzerinden çağıran koda taşınabilir Bir sonuç üreten asenkron işlemler
void Metot çağrısı beklenemez Çağıran kod tarafından bir görev üzerinden izlenemez Temel olarak uygun imzayı gerektiren event handler’lar

Task<T>, tamamlanma bilgisinin yanında bir sonuç da taşır:

async Task<int> SayiGetirAsync()
{
    await Task.Delay(100);
    return 42;
}

int sonuc = await SayiGetirAsync();

Buna karşılık aşağıdaki kullanım geçerli değildir; çünkü BildirimGoster çağrısı beklenebilir bir değer üretmez:

async void BildirimGoster()
{
    await BildirimHazirlaAsync();
}

// await BildirimGoster(); // Beklenebilir değer yoktur.

Genel uygulama mantığında async Task ve sonuç gerekiyorsa async Task<T> tercih edilmelidir. async void bütünüyle hatalı değildir; dönüş tipinin void olmasını gerektiren event handler imzaları özel ve meşru bir kullanım alanı oluşturur.

try/catch neden async void çağrısındaki hatayı yakalayamaz?

async Task metotlarında istisna, dönen Task nesnesi üzerinde tutulur ve çağıran kod await kullandığında yeniden oluşturulur. Bu nedenle await ifadesi bir try bloğunun içindeyse hata, çağıran katmanda yakalanabilir. async void metodunda ise çağırana aktarılabilecek bir Task bulunmaz; metot beklenemez ve istisna doğal biçimde dışarıdaki catch bloğuna taşınamaz.

Çağıran metot
    └── await edilen Task
            └── hata Task üzerinde tutulur
                    └── await noktasındaki try/catch gözlemler

Çağıran metot
    └── async void çağrısı
            └── beklenebilir Task yok
                    └── dış try/catch akışın tamamını kapsayamaz

Bu yüzden aşağıdaki iki çağrı görünüşte benzer olsa da hata akışları farklıdır:

  • await IslemAsync(): Çağıran kod tamamlanmayı ve istisnayı takip eder.
  • IslemVoidAsync(): Çağıran kod yalnızca metodu başlatır; tamamlanma, hata ve uygulamanın kapanma zamanı arasında doğrudan bir takip ilişkisi kurulmaz.

Aynı durum async Task metodu çağrılıp await edilmediğinde de kısmen ortaya çıkar. Bu kez çağıran kodun elinde bir Task vardır; ancak onu beklemediği için hata o anda dışarıdaki try/catch bloğuna ulaşmaz. Teşhis için sorulacak ilk soru şudur: Çağıran kod gerçekten bekliyor mu? Beklemiyorsa hata yönetimi ve işlem ömrü çağıran katmanın kontrolünden çıkmış olabilir.

İki kısa C# örneğiyle bekleme ve hata akışını görün

İki kısa C# örneğiyle bekleme ve hata akışını görün

1. async Task ile hata çağırana taşınır

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        try
        {
            await GecikmeliIslemAsync();
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"Hata yakalandı: {ex.Message}");
        }
    }

    static async Task GecikmeliIslemAsync()
    {
        await Task.Delay(100);
        throw new InvalidOperationException("İşlem tamamlanamadı.");
    }
}

Beklenen çıktı:

Hata yakalandı: İşlem tamamlanamadı.

Burada await, GecikmeliIslemAsync metodunun tamamlanmasını bekler. İstisna, await noktasında yeniden oluşturulduğu için dışarıdaki catch bloğu çalışır.

2. async void ile hata yönetimi metodun içine taşınır

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        try
        {
            BaslatVeHataYakala();
            Console.WriteLine("Çağıran metot devam etti.");
        }
        catch (Exception)
        {
            Console.WriteLine("Dış catch çalıştı.");
        }

        await Task.Delay(300);
    }

    static async void BaslatVeHataYakala()
    {
        try
        {
            await Task.Delay(100);
            throw new InvalidOperationException("async void içinde hata.");
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"İç catch çalıştı: {ex.Message}");
        }
    }
}

Beklenen çıktı:

Çağıran metot devam etti.
İç catch çalıştı: async void içinde hata.

Dış try/catch burada hatayı yakalamaz; çünkü çağıran kodun bekleyebileceği bir Task yoktur. Son Task.Delay yalnızca konsol uygulamasının, fire-and-forget işlemi tamamlanmadan kapanmasını önlemek içindir. Gerçek bir uygulamada bu kontrol kopukluğu; eksik log, yarım kalan işlem veya beklenmedik yaşam döngüsü sorunlarına yol açabilir. Bu nedenle async void kullanılıyorsa hata yönetimi metodun içinde açıkça yapılmalıdır.

async void ne zaman meşru bir tercihtir?

async void, esas olarak çağrı sözleşmesinin void dönüşünü zorunlu kıldığı asenkron event handler senaryolarında meşru bir tercihtir. Kullanıcı bir düğmeye bastığında, pencere yüklendiğinde veya bir arayüz olayı tetiklendiğinde framework, handler metodunu belirli bir imzayla çağırabilir. Bu imza Task döndürmeye izin vermiyorsa handler içinde await kullanılabilen seçenek async void olabilir.

Bu akışta olay başlatılır, handler çalışır ve handler içindeki asenkron işlem await noktasında beklemeye geçebilir. Ancak olayı başlatan kod çoğu zaman handler’ın tamamlanmasını bir Task üzerinden bekleyemez. Bu nedenle hata yönetimi de normal bir async Task çağrısından farklı düşünülmelidir. Handler’ın kendi sınırları içinde uygun bir try/catch kullanmak, hatayı kullanıcıya göstermek, kayıt altına almak veya uygulamanın ilgili hata mekanizmasına aktarmak gerekebilir.

Bunun dışında uygulama servislerinde, yardımcı metotlarda, iş kurallarında ve test edilecek kodlarda genellikle async Task daha izlenebilir bir çağrı zinciri sağlar. Çağıran kod işlemi await edebilir, tamamlanmayı bekleyebilir ve oluşan istisnayı kendi hata yönetimi içinde ele alabilir. Bu yaklaşım, asenkron akışın hangi noktada devam ettiğini ve işlemin ne zaman bittiğini anlamayı kolaylaştırır.

Seçim kontrol listesi

  • Bu metot gerçekten bir event handler mı? Framework tarafından zorunlu tutulan bir imza yoksa async Task seçeneğini değerlendir.
  • Çağıran kod tamamlanmayı beklemeli mi? Beklemeliyse metot, beklenebilir bir Task döndürmelidir.
  • Hata üst katmana taşınmalı mı? Taşınacaksa async Task çağrı zincirini korur; async void bu zinciri kesebilir.

Birim testlerde async Task seçimi ve teşhis şeması

Birim testlerde async Task yaklaşımı tercih edilir; çünkü test metodu asenkron işlemi await edebilir, işlemin tamamlanmasını bekleyebilir ve başarısızlıkları test akışına taşıyabilir. Böylece test, işlem henüz bitmeden tamamlanmış sayılmaz. Ayrıca hata oluştuğunda istisnanın hangi çağrıdan geldiğini izlemek daha kolay olur.

Test edilemeyen veya beklenmedik biçimde başarılı görünen bir asenkron akışta şu sırayla ilerle:

  1. İmza: Test edilen metot async void mı, async Task mı? İlk kontrol dönüş tipidir.
  2. Çağrı: Çağıran kod gerçekten bir Task alıyor mu? Alınmıyorsa tamamlanma ve hata bilgisi kaybolabilir.
  3. Bekleme: Elde edilen Task await ediliyor mu? Edilmeyen çağrı, testin asenkron işlemi beklemeden bitmesine yol açabilir.
  4. İstisna: try/catch bloğu doğru çağrı zincirini kapsıyor mu? await edilmeyen veya async void bir akışta dışarıdaki blok beklenen hatayı yakalayamayabilir.
  5. Test tamamlanması: Test altyapısı, metodun döndürdüğü Task tamamlanana kadar gerçekten bekliyor mu?

Bu süreci İmza, çağrı, bekleme, istisna, test teşhis şeması olarak düşünebilirsin: önce metodun dönüş tipini, sonra çağrının Task üretip üretmediğini, ardından await kullanımını, hata kapsamını ve son olarak testin tamamlanma koşulunu kontrol et.

Düzenli pratik yaparken hangi C# konularını ne ölçüde bildiğini görmek için C# bilgi testlerinden yararlanabilirsin. Bu testler bir eğitim programı değil, mevcut seviyeni kontrol etmeye ve çalışma konularını belirlemeye yardımcı olan bir araçtır.

Kısa seçim kontrol listesi

  • Event handler imzası zorunluysa async void düşünülebilir.
  • İş mantığı ve yardımcı metotlarda async Task kullan.
  • Test edilecek metotların tamamlanabilir ve beklenebilir olmasına dikkat et.
  • Hatanın hangi katmanda ele alınacağını önceden belirle.

Sık Sorulan Sorular

async void metot await edilebilir mi?

Hayır. async void bir Task döndürmediği için çağıran kod tarafından doğrudan await edilemez. Bu nedenle tamamlanma ve hata takibi sınırlıdır.

async Task çağrısını await etmezsem ne olur?

Asenkron işlem arka planda devam edebilir; ancak çağıran kod tamamlanmasını beklemez. Test veya üst katman erken bitebilir ve oluşan istisna beklenen hata akışına taşınmayabilir.

async void içindeki istisna neden dışarıdaki try/catch bloğuna ulaşmayabilir?

Çağıran kod async void metodunu bekleyemediği için istisna, normal Task çağrı zinciri üzerinden dışarı taşınmaz. Hatanın handler içinde veya ilgili özel mekanizmada ele alınması gerekir.

Event handler dışında async void kullanmak ne zaman sorun oluşturur?

Çağıran kodun tamamlanmayı beklemesi, sonucu izlemesi veya hatayı üst katmana aktarması gerektiğinde sorun oluşturur. Bu durumlarda async Task daha güvenli ve izlenebilir bir seçimdir.

Asenkron bir metot birim testte neden async Task döndürmelidir?

Test metodu böylece işlemi await edebilir, tamamlanmasını bekleyebilir ve oluşan istisnayı test sonucuna yansıtabilir. Bu, testin işlem bitmeden başarılı görünmesini önlemeye yardımcı olur.

Özetle, async void event handler gibi zorunlu imza gerektiren dar bir bağlamda kullanılabilir; uygulamanın geri kalanında ise async Task tercih etmek bekleme, hata yakalama ve test edilebilirlik açısından daha sağlıklı bir akış sağlar.

Bu içerik aradığın cevabı verdi mi?
Yanıtın, hangi yazıları geliştirmemiz gerektiğini anlamamıza yardımcı olur.
Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

Bu konudan sonra ne okuyabilirsin?

Tüm yazılar

İlgili Eğitimler

Berk Keskin, yazılım geliştirici ve eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; İzmir Ekonomi Üniversitesi'ni bölüm birincisi ve yüksek şeref öğrencisi olarak tamamladı. Bugün yalnızca eğitim vermekle kalmıyor, sektörde aktif olarak yazılım projeleri geliştiriyor ve gerçek dünya deneyimini birebir derslerine taşıyor. Ezberden uzak, mühendislik zihniyetini merkeze alan sürdürülebilir öğrenme sistemleri tasarlayarak sorgulayan, üreten ve problem çözebilen yeni nesil yazılımcılar yetiştiriyor.

Sektörel Deneyim & Projeler

  • Ticarify Entegrasyon Yazılım logosu CEO Ticarify Entegrasyon YazılımPazaryerleri ve e-ticaret sitelerine otomatik e-fatura kesimi, sipariş ve kargo takibi hizmetleri sunan e-Dönüşüm platformunun API mimarisini ve yazılım ekibini yönetmektedir.
  • Benim Düğünüm logosu CEO Benim DüğünümDijital etkinlik ve anı paylaşım platformu.
  • Siberdizayn logosu Yazılım Ekibi Lideri SiberdizaynYüksek anlık oyuncu trafiğine sahip oyun kontrol panelleri ve sunucu altyapıları geliştiren yazılım ekibine liderlik etmektedir.
  • MEDYOGRAFYA 360° Dijital Çözümler logosu Dijital Strateji Lideri MEDYOGRAFYA 360° Dijital ÇözümlerŞirketlerin dijital çözümlerde uzun vadede nasıl ilerlemesi gerektiği ve dijital dönüşüm süreçlerinin yönetilmesine destek olmaktadır.
  • İzmir Ekonomi Üniversitesi logosu Danışma Kurulu Üyesi İzmir Ekonomi ÜniversitesiMezun olduğu üniversitesinde, Bilgisayar Programcılığı bölümünün akademik müfredatını güncel sektör ihtiyaçlarına göre şekillendirmek adına Danışma Kurulu'nda görev almaktadır.
WhatsApp Hemen Ara