Belajar .NET - Asynchronous Programming & Concurrency
Series/Belajar .NET/Episode 14
Episode 14 of 23

Belajar .NET - Asynchronous Programming & Concurrency

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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.

Task-Based Async Pattern

Dasar async dan await

Kata kunci async menandai method yang bisa dihentikan sementara, dan await menunggu operasi asinkron tanpa memblokir thread:

Method async
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>.

Alur Eksekusi

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, ValueTask, dan Cancellation Tokens

Task dan ValueTask

Task adalah representasi operasi yang sedang berjalan. Untuk operasi yang sering menyelesaikan secara sinkron (misalnya dari cache), ValueTask mengurangi alokasi:

ValueTask untuk path cepat
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.

Cancellation Tokens

Operasi panjang harus bisa dibatalkan. CancellationToken menyebarkan sinyal pembatalan:

Dukungan 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.

Parallel Programming

Parallel.For untuk CPU-Bound

Untuk pekerjaan berat CPU yang independen, Parallel.For membagi kerja ke banyak thread:

Parallel.For
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.

Channels untuk Producer-Consumer

Channel mengimplementasikan queue aman-thread untuk pola producer-consumer:

Membuat channel
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.

Thread Safety dan Synchronization Primitives

Race Condition dan Lock

Saat banyak thread menulis data yang sama, hasil bisa tidak konsisten. lock membatasi akses ke satu thread:

Sinkronisasi dengan lock
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.

Memilih Alat yang Tepat

Panduan:

  • lock: melindungi mutasi kecil pada state bersama.
  • ConcurrentDictionary: koleksi kunci-nilai yang aman-thread.
  • Channel: pipeline producer-consumer dengan backpressure.
  • SemaphoreSlim: membatasi akses bersamaan, misalnya ke sumber daya terbatas.
  • Interlocked: operasi atomik sederhana seperti increment.

Warning

Jangan membuat lock di dalam async code — lock memblokir thread. Gunakan SemaphoreSlim dengan await WaitAsync() untuk sinkronisasi dalam konteks asinkron.

Ringkasan Praktik Konkurensi

  • Gunakan async/await untuk I/O; thread pool menangani sisanya.
  • Teruskan CancellationToken dari permintaan hingga operasi I/O.
  • Parallel.For untuk CPU-bound yang independen.
  • Channels untuk memisahkan produksi dan konsumsi pesan.
  • Pilih koleksi concurrent alih-alih lock manual bila memungkinkan.

Penutup

Inti yang harus dibawa pulang:

  • async/await membuat operasi I/O tidak memblokir thread.
  • Task mewakili operasi asinkron; ValueTask hemat alokasi di path cepat.
  • CancellationToken memungkinkan pembatalan yang rapi.
  • Parallel.For mempercepat kerja CPU-bound yang independen.
  • Channels menghubungkan producer dan consumer dengan aman-thread.
  • lock, ConcurrentDictionary, dan SemaphoreSlim menjaga thread safety.

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.

Belajar .NET - Asynchronous Programming & Concurrency | Belajar .NET