Pernyataan await menunda sisa eksekusi sebuah metode tanpa memblokir thread yang menjalankannya, lalu melanjutkannya secara otomatis begitu pekerjaan yang ditunggu selesai. Topik ini membahas mental model di balik pasangan async/await, yaitu cara kompilator mengubah metode async menjadi mesin state (state machine) beserta pilihan tipe kembalian yang sah untuk metode async. Setelah membacanya, alur eksekusi program async dapat dilacak langkah demi langkah dan tipe kembalian yang tepat dapat dipilih untuk setiap kebutuhan.
Alur eksekusi metode async#
Metode async berjalan serempak (synchronous) hingga menemui await pertama. Pada titik itu kendali dikembalikan kepada pemanggil, sedangkan sisa metode disusun menjadi kelanjutan (continuation), yaitu potongan kode yang dijalankan setelah pekerjaan yang di-await selesai. Contoh berikut memakai Task.Delay untuk menyimulasikan pekerjaan yang butuh waktu, misalnya permintaan jaringan atau pembacaan berkas. Salin programnya ke berkas Program.cs, lalu jalankan dengan perintah dotnet run --file Program.cs.
// Program pendalaman async/await: alur eksekusi dari mulai hingga selesai.
async Task<string> SiapkanLaporanAsync(string nama, int durasiMs)
{
Console.WriteLine($"{nama}: mulai menyiapkan laporan");
await Task.Delay(durasiMs);
Console.WriteLine($"{nama}: laporan selesai disiapkan");
return $"laporan {nama}";
}
Console.WriteLine("Program dimulai");
string laporanA = await SiapkanLaporanAsync("A", 100);
string laporanB = await SiapkanLaporanAsync("B", 50);
Console.WriteLine($"Diperoleh: {laporanA} dan {laporanB}");
Console.WriteLine("Program selesai");Program dimulai
A: mulai menyiapkan laporan
A: laporan selesai disiapkan
B: mulai menyiapkan laporan
B: laporan selesai disiapkan
Diperoleh: laporan A dan laporan B
Program selesaiKeluaran mengikuti urutan yang pasti. Kedua laporan menjalani siklus mulai dan selesai satu per satu karena await dipanggil langsung pada setiap pemanggilan SiapkanLaporanAsync, sehingga laporan A benar-benar selesai sebelum laporan B dimulai. Nilai kembali tiap pemanggilan juga dapat diambil seketika setelah await, yaitu teks laporan A dan laporan B pada baris keenam. Apabila kedua laporan justru ingin dijalankan bersamaan, keduanya dimulai terlebih dahulu lalu digabungkan dengan Task.WhenAll sebagaimana dibahas pada topik Task dan Task<T>.
Bentuk tanda tangan metodenya juga patut dicermati. Penanda async ditempatkan pada deklarasi metode, tipe kembaliannya berupa Task<string>, dan pernyataan return menuliskan string biasa. Kompilator membungkus nilai kembalian itu ke dalam Task<string> secara otomatis, sedangkan pada sisi pemanggil, await mengambil nilainya kembali sebagai string. Tanda tangan ini konsisten untuk semua metode async yang menghasilkan nilai.
Mesin state di balik await#
Kata kunci async tidak membuat metode berjalan pada thread baru. Kompilator menulis ulang seluruh badan metode async menjadi sebuah mesin state (state machine), yaitu objek yang menyimpan posisi eksekusi, nilai variabel lokal, dan pekerjaan yang sedang ditunggu. Setiap await membelah metode menjadi segmen, dan mesin state berpindah dari satu segmen ke segmen berikutnya saat pekerjaan yang ditunggu selesai. Mesin inilah yang membuat sisa metode dapat dilanjutkan tepat dari titik terakhirnya, seolah-olah eksekusi tidak pernah terputus.
Alur kerjanya berlangsung sebagai berikut.
- Metode async dipanggil, dan segmen pertamanya dijalankan serempak pada thread pemanggil.
- Saat menemui await atas pekerjaan yang belum selesai, mesin state mencatat posisinya, mendaftarkan kelanjutan pada pekerjaan tersebut, lalu mengembalikan Task kepada pemanggil.
- Pekerjaan yang ditunggu selesai, dan kelanjutannya membangunkan mesin state untuk melanjutkan segmen berikutnya, kemungkinan pada thread yang berbeda dari semula.
- Ketika segmen terakhir selesai, Task yang diserahkan kepada pemanggil ditandai selesai beserta hasilnya.
Proses ini menjelaskan dua sifat metode async. Pertama, tidak ada thread yang diblokir selama menunggu, karena thread yang menemui await langsung bebas menjalankan pekerjaan lain. Kedua, thread yang melanjutkan eksekusi belum tentu thread semula, karena kelanjutan diantrekan pada thread pool (thread pool), yaitu kumpulan thread pekerja yang dikelola runtime .NET.
Tipe kembalian metode async#
Tipe kembalian menentukan bentuk pekerjaan yang diserahkan metode async kepada pemanggil. Tipe Task dipakai bila metode tidak menghasilkan nilai, tipe Task<T> bila metode menghasilkan satu nilai, dan void hanya dipakai untuk penangan event (event handler) karena pemanggil tidak dapat menunggu maupun menangkap error darinya. Ketiganya dijalankan pada contoh berikut.
// Contoh: tiga tipe kembalian metode async.
async Task CatatAsync()
{
await Task.Delay(50);
Console.WriteLine("Pencatatan selesai");
}
async Task<int> HitungAsync()
{
await Task.Delay(50);
return 42;
}
async ValueTask<int> HitungCepatAsync(bool perluHitung)
{
if (!perluHitung)
{
return 0; // selesai serempak tanpa menyentuh thread lain
}
return await HitungAsync();
}
await CatatAsync();
int hasil = await HitungAsync();
Console.WriteLine($"HitungAsync mengembalikan {hasil}");
int nol = await HitungCepatAsync(false);
Console.WriteLine($"Jalur cepat tanpa hitung: {nol}");
int hasilCepat = await HitungCepatAsync(true);
Console.WriteLine($"Jalur dengan hitung: {hasilCepat}");Pencatatan selesai
HitungAsync mengembalikan 42
Jalur cepat tanpa hitung: 0
Jalur dengan hitung: 42Contoh di atas memperlihatkan satu tipe kembalian tambahan, yaitu ValueTask<T>. Tipe ini ditujukan bagi metode yang sering selesai tanpa menunggu, misalnya membaca dari cache, karena hasilnya dapat dikembalikan serempak tanpa membuat objek Task terlebih dahulu. Pemanggilan HitungCepatAsync dengan argumen false mengembalikan nilai 0 melalui jalur cepat tersebut. Kelemahannya, satu ValueTask hanya boleh di-await satu kali, sehingga Task<T> tetap menjadi pilihan default untuk metode pustaka umum.
Kesalahan umum#
Empat kesalahan berikut paling sering muncul ketika metode async ditulis.
- Memilih void sebagai tipe kembalian di luar event handler. Error yang terjadi di dalam metode async void tidak dapat ditangkap pemanggil dan biasanya menghentikan proses, sehingga Task selalu dipilih agar pemanggil dapat menunggu sekaligus menangani errornya.
- Mengira setiap metode async berjalan pada thread baru. Metode async tetap berjalan pada thread pemanggil hingga await pertama, dan pekerjaan I/O bahkan tidak memakai thread sama sekali selama menunggu.
- Memanggil metode async tanpa menunggu hasilnya. Pemanggilan tanpa await berlangsung sekejap lalu program berlanjut, sedangkan error pada pekerjaan itu menghilang tanpa tercatat. Kompilator memberi peringatan CS4014 atas pola ini.
- Menganggap kode setelah await dijamin berjalan pada thread yang sama. Kelanjutan diantrekan pada thread pool, sehingga keadaan yang bergantung pada thread tertentu memerlukan penanganan tersendiri.
Ringkasan#
- Metode async berjalan serempak hingga await pertama, lalu mengembalikan kendali kepada pemanggil dan melanjutkan sisanya sebagai kelanjutan.
- Kompilator menulis ulang metode async menjadi mesin state yang menyimpan posisi eksekusi dan variabel lokal, sehingga sisa metode dapat dilanjutkan setelah pekerjaan yang ditunggu selesai.
- Tipe kembalian yang sah untuk metode async adalah
Task,Task<T>,voiduntuk event handler, sertaValueTask<T>untuk jalur yang sering selesai serempak dan hanya di-await satu kali.
Referensi#
- Asynchronous programming — Microsoft Learn — Panduan resmi pemrograman asinkron C# beserta model task asynchronous programming.
- Async return types — Microsoft Learn
— Penjelasan
Task,Task<T>,void,ValueTask<T>, danIAsyncEnumerablesebagai tipe kembalian metode async.