Bab Iterator Pattern menunjukkan bahwa yield return mengubah method biasa menjadi iterator sinkron; compiler yang membangun state machine-nya. Kata kunci yang sama juga bekerja untuk sisi asinkron: sebuah method async dengan return type IAsyncEnumerable<T> boleh memuat yield return, dan compiler membangun async state machine yang mengirim item satu per satu, lengkap dengan titik tunggu await di antara item.

Aturan Async Iterator Method #

Sebuah method menjadi async iterator bila memenuhi syarat berikut:

  • Return type-nya IAsyncEnumerable<T>; IAsyncEnumerator<T> tidak dapat menjadi return type method.
  • Method ditandai async dan boleh memuat await di dalam badannya.
  • yield return boleh bercampur dengan await; setiap item boleh menunggu pekerjaan asinkron sebelum dikirim.
  • Pemanggil tidak men-await hasil method itu sendiri; hasilnya adalah urutan yang ditelusuri dengan await foreach.

Tidak ada yield return yang dipanggil lebih awal: item baru dikirim ketika konsumen meminta item berikutnya, sehingga kerja asinkron pun berjalan sesuai permintaan.

Contoh: Laporan Stok Bertahap #

Method berikut menyusun laporan dari tiga produk; setiap baris stok menunggu hasil kueri asinkron sebelum dikirim:

csharp
static async Task<int> AmbilStokAsync(string nama)
{
    await Task.Delay(20);
    return nama.Length * 10;
}

static async IAsyncEnumerable<string> SusunLaporanAsync(string[] daftarProduk)
{
    yield return "== Mulai laporan stok ==";
    foreach (string nama in daftarProduk)
    {
        int stok = await AmbilStokAsync(nama);
        yield return $"{nama}: stok {stok}";
    }
    yield return "== Akhir laporan stok ==";
}

// Pemanggil:
string[] produk = { "Kopi Gayo", "Teh Melati", "Gula Pasir" };

await foreach (string baris in SusunLaporanAsync(produk))
{
    Console.WriteLine(baris);
}

Output yang Dihasilkan:

text
== Mulai laporan stok ==
Kopi Gayo: stok 90
Teh Melati: stok 100
Gula Pasir: stok 100
== Akhir laporan stok ==

Mari kita bedah alurnya:

  1. yield return "== Mulai laporan stok ==": item pertama dikirim tanpa menunggu apa pun; konsumen langsung menerimanya.
  2. int stok = await AmbilStokAsync(nama): di antara dua item, iterator menunggu kueri stok; bagian inilah yang membedakannya dari iterator sinkron.
  3. yield return $"{nama}: stok {stok}": baris stok dikirim tepat setelah kueri selesai; panjang nama dikali 10 menghasilkan stok 90 dan 100.
  4. yield return "== Akhir laporan stok ==": item penutup dikirim setelah loop selesai, lalu iterator berakhir dan await foreach pun berhenti.

Sisi data sudah lengkap. Bab berikutnya memindahkan pola asinkron ini ke dunia berkas dengan API file yang asinkron.