Episode ini membahas async/await, Task dan ValueTask, cancellation tokens, parallel programming dengan Parallel.For dan Channels, serta thread safety dan synchronization primitives. Kalian akan menulis kode I/O yang responsif tanpa memblokir thread.

Operasi I/O — membaca file, memanggil API, query database — adalah bagian terbesar waktu eksekusi aplikasi. Episode 14 mengajarkan cara menjalankan operasi ini tanpa memblokir thread: pola async/await, Task, cancellation, dan pemrograman paralel.
Pentingnya tidak bisa diremehkan: web server yang memblokir thread saat menunggu database akan kehabisan thread dengan cepat. Dengan async, thread dilepaskan untuk melayani permintaan lain saat operasi I/O menunggu. Ini adalah perbedaan antara API yang lambat dan API yang melayani ribuan request bersamaan.
Kata kunci async menandai method yang bisa dihentikan sementara, dan await menunggu operasi asinkron tanpa memblokir thread:
public async Task<List<Produk>> GetProdukAsync()
{
using var db = new AppDbContext();
return await db.Produk
.Where(p => p.Stok > 0)
.ToListAsync();
}await db.Produk...ToListAsync() melepaskan thread selama query database berjalan dan melanjutkannya saat hasil tersedia. ToListAsync adalah versi asinkron dari ToList. Metode yang memakai await harus bertanda async dan mengembalikan Task atau Task<T>.
Saat await dijalankan, kontrol kembali ke pemanggil dan thread bebas untuk bekerja lain. Saat operasi selesai, eksekusi dilanjutkan — biasanya di thread pool yang sama. Inilah mengapa async membuat aplikasi lebih skalabel, bukan sekadar lebih cepat.
Task adalah representasi operasi yang sedang berjalan. Untuk operasi yang sering menyelesaikan secara sinkron (misalnya dari cache), ValueTask mengurangi alokasi:
public ValueTask<Produk> GetCachedAsync(int id)
{
if (_cache.TryGetValue(id, out var produk))
{
return ValueTask.FromResult(produk);
}
return LoadFromDbAsync(id);
}ValueTask.FromResult mengembalikan nilai langsung tanpa alokasi Task. Gunakan ValueTask hanya saat profil menunjukkan tekanan alokasi; Task tetap pilihan default yang aman.
Operasi panjang harus bisa dibatalkan. CancellationToken menyebarkan sinyal pembatalan:
public async Task<string> CallApiAsync(CancellationToken token)
{
using var http = new HttpClient();
return await http.GetStringAsync("https://api.example.com", token);
}CancellationToken diteruskan ke HttpClient dan database. Saat klien memutus koneksi, framework menandai token dan operasi dihentikan lebih awal — menghemat sumber daya server. Selalu terima dan teruskan CancellationToken dari parameter sampai ke operasi I/O.
Untuk pekerjaan berat CPU yang independen, Parallel.For membagi kerja ke banyak thread:
var hasil = new int[1_000_000];
Parallel.For(0, hasil.Length, i =>
{
hasil[i] = Compute(i);
});Parallel.For(0, hasil.Length, ...) mengeksekusi lambda pada banyak thread sekaligus. Cocok untuk transformasi data besar yang tidak saling bergantung. Jangan gunakan untuk I/O — untuk itu async adalah pilihan yang lebih tepat.
Channel mengimplementasikan queue aman-thread untuk pola producer-consumer:
var channel = Channel.CreateUnbounded<string>();
await channel.Writer.WriteAsync("pesan 1");
await channel.Reader.ReadAsync();Channel.CreateUnbounded<string>() membuat queue; Writer memasukkan item dan Reader mengeluarkannya. Pola ini menjadi dasar pipeline pemrosesan — banyak worker membaca dari satu channel dan memproses item secara paralel.
Saat banyak thread menulis data yang sama, hasil bisa tidak konsisten. lock membatasi akses ke satu thread:
private readonly object _lock = new();
private int _penghitung;
public void Tambah()
{
lock (_lock)
{
_penghitung++;
}
}lock (_lock) memastikan hanya satu thread yang masuk blok pada satu waktu. Untuk koleksi bersamaan, pertimbangkan ConcurrentDictionary dan ConcurrentQueue yang dirancang aman-thread sejak awal — lebih cepat dan kurang rawan error daripada lock manual.
Panduan:
Warning
Jangan membuat lock di dalam async code — lock memblokir thread. Gunakan SemaphoreSlim dengan await WaitAsync() untuk sinkronisasi dalam konteks asinkron.
Inti yang harus dibawa pulang:
Di episode 15 selanjutnya kita akan membahas performance dan diagnostics — profiling dengan dotnet-trace dan dotnet-counters, memory diagnostics dan GC tuning, Span dan Memory untuk alokasi minimal, serta hot reload, tiered compilation, dan pertimbangan AOT.