await foreach terasa sederhana, tetapi di baliknya bekerja sepasang interface dengan kontrak yang jelas. IAsyncEnumerable<T> mewakili urutan asinkron dan membuka satu enumerator lewat method GetAsyncEnumerator(). Enumerator itu, bertipe IAsyncEnumerator<T>, menyediakan MoveNextAsync() untuk meminta item berikutnya, properti Current untuk membacanya, dan DisposeAsync() untuk membersihkan resource. Bab ini menelusuri interface tersebut secara manual agar mekanisme await foreach terlihat jelas.
Anatomi Dua Interface #
IAsyncEnumerable<T>: hanya punya satu method,GetAsyncEnumerator(CancellationToken), yang mengembalikan enumerator untuk satu kali penelusuran.IAsyncEnumerator<T>: punyaMoveNextAsync()yang mengembalikanValueTask<bool>(true berarti ada item), propertiCurrentbertipeT, danDisposeAsync()bertipeValueTask.
Perbandingan ini menegaskan posisinya di samping sepasang sinkronnya:
| Pasangan sinkron | Pasangan asinkron | Langkah ambil item |
|---|---|---|
IEnumerable<T> | IAsyncEnumerable<T> | GetAsyncEnumerator() |
IEnumerator<T> | IAsyncEnumerator<T> | MoveNext() vs MoveNextAsync() |
Contoh: Menelusuri Antrean Notifikasi Secara Manual #
Kode berikut mengambil enumerator, menelusurinya dengan loop while, lalu membiarkan await using membersihkannya:
static async IAsyncEnumerable<string> AmbilNotifikasiAsync()
{
await Task.Delay(30);
yield return "Pesanan baru INV-201";
await Task.Delay(30);
yield return "Stok Kopi Gayo menipis";
}
// Pemanggil:
IAsyncEnumerable<string> antrean = AmbilNotifikasiAsync();
await using IAsyncEnumerator<string> enumerator = antrean.GetAsyncEnumerator();
while (await enumerator.MoveNextAsync())
{
Console.WriteLine($"Notifikasi masuk: {enumerator.Current}");
}
Console.WriteLine("Antrean notifikasi habis");Output yang Dihasilkan:
Notifikasi masuk: Pesanan baru INV-201
Notifikasi masuk: Stok Kopi Gayo menipis
Antrean notifikasi habisMari kita bedah baris-barisnya:
antrean.GetAsyncEnumerator(): pemanggilan method produsen baru membuat urutan; enumerator diambil dari urutan itu dan belum ada item yang dikirim.await using ... enumerator: deklarasiawait usingmemastikanDisposeAsync()dipanggil saat blok selesai; padanan asinkron dariusing.await enumerator.MoveNextAsync(): setiap pemanggilan menunggu item berikutnya; hasiltrueberarti satu item siap diCurrent.enumerator.Current: properti menyimpan item terakhir yang berhasil diambil; pembacaannya hanya sah setelahMoveNextAsyncmengembalikantrue.- Keluar loop:
MoveNextAsyncmengembalikanfalseketika produsen habis, danawait usingmenjalankan pembersihan.
Ketahuan cara mengonsumsi urutan asinkron. Bab berikutnya berfokus pada sisi produsen: menulis method yang mengirim item asinkron dengan yield return.