Operasi asinkron jarang berdiri sendiri; satu langkah sering membutuhkan hasil langkah sebelumnya, misalnya mengambil nama produk baru menghitung panjang namanya, atau mengambil harga baru menentukan ongkir. Komposisi task adalah praktik menyusun langkah-langkah itu menjadi satu alur utuh. C# menyediakan dua cara utama: await berantai di dalam method asinkron, dan sambungan eksplisit lewat method ContinueWith.

Dua Cara Merangkai #

  • await berantai: setiap langkah menunggu langkah sebelumnya; alur terbaca dari atas ke bawah seperti kode sinkron, dan compiler yang menyusun kelanjutannya.
  • ContinueWith: pemanggil melampirkan kelanjutan pada sebuah task; kelanjutan menerima task sebelumnya sebagai argumen dan dapat mengembalikan task baru untuk dirangkai lebih lanjut.

await berantai adalah pilihan default karena paling mudah dibaca. ContinueWith berguna ketika kelanjutan perlu disusun secara dinamis oleh kode lain.

Contoh: Alur Hitung Total Keranjang #

Kode berikut merangkai dua pola: lanjutan ContinueWith pada satu task, dan pipeline await untuk menghitung total belanja:

csharp
static async Task<string> AmbilNamaProdukAsync()
{
    await Task.Delay(40);
    return "Kopi Gayo";
}

static async Task<int> AmbilHargaAsync()
{
    await Task.Delay(30);
    return 85000;
}

static async Task<int> HitungOngkirAsync(int harga)
{
    await Task.Delay(30);
    return harga >= 50000 ? 0 : 20000;
}

static async Task<int> HitungTotalKeranjangAsync()
{
    int harga = await AmbilHargaAsync();
    int ongkir = await HitungOngkirAsync(harga);
    return harga + ongkir;
}

// Pemanggil:
Task<string> ambil = AmbilNamaProdukAsync();
Task<int> hitungPanjang = ambil.ContinueWith(t =>
{
    Console.WriteLine($"Lanjutan menerima: {t.Result}");
    return t.Result.Length;
});

int panjang = await hitungPanjang;
Console.WriteLine($"Panjang nama produk: {panjang}");

int total = await HitungTotalKeranjangAsync();
Console.WriteLine($"Total keranjang: Rp{total}");

Output yang Dihasilkan:

text
Lanjutan menerima: Kopi Gayo
Panjang nama produk: 9
Total keranjang: Rp85000

Mari kita bedah komposisinya:

  1. ambil.ContinueWith(t => ...): kelanjutan terlampir pada task nama produk; badan lambda baru berjalan setelah AmbilNamaProdukAsync selesai.
  2. t.Result di dalam lanjutan: aman diakses karena task sebelumnya sudah RanToCompletion; lambda mengembalikan panjang nama, yaitu 9 karakter.
  3. await hitungPanjang: pemanggil mengeluarkan hasil kelanjutan dari objek Task<int> yang dirangkai.
  4. HitungTotalKeranjangAsync: pipeline dua langkah; nilai harga mengalir ke HitungOngkirAsync, dan ongkir 0 muncul karena harga 85000 melewati batas 50000.

Merangkai berurutan sudah tertangani. Bab berikutnya membahas menjalankan banyak task secara serentak dengan Task.WhenAll dan Task.WhenAny.