Exception dalam kode asinkron tidak melompat langsung ke pemanggil. Ketika method asinkron melempar exception, runtime menangkapnya, menyimpannya di dalam objek Task, dan mengubah status task menjadi Faulted. Exception itu baru dilempar ulang ke titik await yang menunggu task tersebut. Pola ini menjelaskan mengapa try/catch di sekitar await cukup untuk menangkap kesalahan yang terjadi jauh di dalam method asinkron lain.

Perjalanan Sebuah Exception #

  • Exception di dalam method asinkron ditangkap runtime dan disimpan pada task yang sedang berjalan.
  • Status task berubah menjadi Faulted; sisa badan method tidak dijalankan.
  • await membaca kembali exception yang tersimpan dan melemparnya pada pemanggil, sehingga blok catch biasa dapat menangkapnya.
  • Pada Task.WhenAll, semua exception tersimpan dalam AggregateException pada task gabungan, tetapi await melempar hanya yang pertama.

Contoh: Menangkap Exception dan Menghitung Sisanya #

Kode berikut menangkap exception pembayaran, lalu menghitung berapa exception yang tercatat ketika dua pengiriman notifikasi gagal bersamaan:

csharp
static async Task<int> ProsesPembayaranAsync(int nominal)
{
    await Task.Delay(50);
    if (nominal <= 0)
    {
        throw new InvalidOperationException("Nominal pembayaran harus positif");
    }
    return nominal;
}

static async Task GagalKirimAsync(string penyebab)
{
    await Task.Delay(30);
    throw new InvalidOperationException(penyebab);
}

// Pemanggil:
Task<int> proses = ProsesPembayaranAsync(-50000);

try
{
    int total = await proses;
    Console.WriteLine($"Pembayaran berhasil: Rp{total}");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"Tertangkap saat await: {ex.Message}");
    Console.WriteLine($"Status task: {proses.Status}");
}

Task gagal1 = GagalKirimAsync("Koneksi database terputus");
Task gagal2 = GagalKirimAsync("Format data tidak sesuai");
Task gabungan = Task.WhenAll(gagal1, gagal2);

try
{
    await gabungan;
}
catch (InvalidOperationException)
{
    Console.WriteLine($"Exception tercatat oleh WhenAll: {gabungan.Exception.InnerExceptions.Count}");
}

Output yang Dihasilkan:

text
Tertangkap saat await: Nominal pembayaran harus positif
Status task: Faulted
Exception tercatat oleh WhenAll: 2

Mari kita bedah perjalanannya:

  1. ProsesPembayaranAsync(-50000): method berjalan sampai pernyataan throw; exception disimpan pada task dan sisa method dilewati.
  2. catch (InvalidOperationException ex): await melempar ulang exception yang tersimpan, sehingga blok catch pemanggil menangkapnya seperti exception sinkron biasa.
  3. proses.Status: status Faulted terbaca setelah catch, membuktikan kesalahan tersimpan pada task.
  4. gabungan.Exception.InnerExceptions.Count: WhenAll mencatat dua exception; await melempar yang pertama, sedangkan keduanya tetap dapat diperiksa melalui properti Exception.

Kesalahan sudah tertangani dengan rapi. Bab berikutnya beralih ke bentuk data asinkron: stream yang mengirim item satu per satu seiring waktu.