Docs β€Ί .NET Development β€Ί Async/Await Programming

Async/Await Programming

Memahami pemrograman asynchronous di C# dengan async dan await

Async/Await Programming

Pemrograman asynchronous adalah teknik yang memungkinkan program Anda melakukan banyak operasi secara bersamaan tanpa memblokir thread utama. Ini sangat penting untuk aplikasi yang responsif.

Mengapa Async?

Masalah dengan Synchronous Code

// Synchronous - memblokir thread
public string GetDataSync()
{
    // Thread menunggu selama 2 detik
    var result = httpClient.GetStringAsync("https://api.example.com").Result;
    return result;
}

Masalah:

  • Thread tidak bisa mengerjakan hal lain
  • Aplikasi β€œfreeze”
  • Resource terbuang sia-sia

Solusi dengan Async

// Asynchronous - thread bebas
public async Task<string> GetDataAsync()
{
    // Thread bisa mengerjakan hal lain selama menunggu
    var result = await httpClient.GetStringAsync("https://api.example.com");
    return result;
}

Konsep Dasar

Task dan Task

// Task = operasi async tanpa return value
public async Task DoSomethingAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Selesai!");
}

// Task<T> = operasi async dengan return value
public async Task<int> CalculateAsync()
{
    await Task.Delay(1000);
    return 42;
}

async dan await Keywords

// async menandakan method adalah asynchronous
// await menunggu operasi async selesai
public async Task ProcessAsync()
{
    Console.WriteLine("Mulai...");
    
    // await = tunggu sampai selesai, tapi thread bebas
    await Task.Delay(2000);
    
    Console.WriteLine("Selesai!");
}

Pattern-Pattern Umum

Sequential Execution

// Operasi dijalankan satu per satu
public async Task SequentialAsync()
{
    var result1 = await GetData1Async();  // Tunggu selesai
    var result2 = await GetData2Async();  // Baru jalankan ini
    var result3 = await GetData3Async();  // Lalu ini
    
    // Total waktu = waktu1 + waktu2 + waktu3
}

Parallel Execution

// Semua operasi dijalankan bersamaan
public async Task ParallelAsync()
{
    // Mulai semua task
    var task1 = GetData1Async();
    var task2 = GetData2Async();
    var task3 = GetData3Async();
    
    // Tunggu semua selesai
    await Task.WhenAll(task1, task2, task3);
    
    // Ambil hasil
    var result1 = task1.Result;
    var result2 = task2.Result;
    var result3 = task3.Result;
    
    // Total waktu = waktu terlama dari ketiganya
}

WhenAll vs WhenAny

// WhenAll - tunggu SEMUA selesai
var results = await Task.WhenAll(task1, task2, task3);

// WhenAny - tunggu SALAH SATU selesai
var firstCompleted = await Task.WhenAny(task1, task2, task3);

Async di ASP.NET Core

Controller

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;
    
    [HttpGet]
    public async Task<ActionResult<List<Product>>> GetAll()
    {
        var products = await _service.GetAllAsync();
        return Ok(products);
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetById(int id)
    {
        var product = await _service.GetByIdAsync(id);
        
        if (product == null)
            return NotFound();
            
        return Ok(product);
    }
}

Service

public class ProductService : IProductService
{
    private readonly HttpClient _httpClient;
    private readonly AppDbContext _context;
    
    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products
            .FirstOrDefaultAsync(p => p.Id == id);
    }
    
    public async Task<List<Product>> GetFromExternalApiAsync()
    {
        var response = await _httpClient.GetAsync("https://api.example.com/products");
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<List<Product>>(content);
    }
}

Error Handling

Try-Catch dengan Async

public async Task<Product?> SafeGetProductAsync(int id)
{
    try
    {
        return await _service.GetByIdAsync(id);
    }
    catch (HttpRequestException ex)
    {
        _logger.LogError(ex, "Gagal mengambil product {Id}", id);
        return null;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error tidak terduga");
        throw; // re-throw untuk error serius
    }
}

Exception di Task.WhenAll

try
{
    await Task.WhenAll(task1, task2, task3);
}
catch (Exception)
{
    // Hanya exception pertama yang di-catch
    // Untuk semua exception:
    if (task1.IsFaulted) HandleError(task1.Exception);
    if (task2.IsFaulted) HandleError(task2.Exception);
    if (task3.IsFaulted) HandleError(task3.Exception);
}

Cancellation

CancellationToken

public async Task<Data> GetDataAsync(CancellationToken cancellationToken = default)
{
    // Cek apakah di-cancel
    cancellationToken.ThrowIfCancellationRequested();
    
    // Pass token ke operasi async
    var response = await _httpClient.GetAsync(url, cancellationToken);
    
    return await response.Content.ReadFromJsonAsync<Data>(cancellationToken);
}

Menggunakan CancellationToken

// Di controller - ASP.NET Core auto-cancel saat request dibatalkan
[HttpGet]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
    var data = await _service.GetDataAsync(cancellationToken);
    return Ok(data);
}

// Manual timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

try
{
    var result = await GetDataAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operasi di-cancel atau timeout");
}

Best Practices

1. Async All The Way

// ❌ JANGAN - blocking async code
public string GetData()
{
    return GetDataAsync().Result;  // Deadlock risk!
}

// βœ… BENAR - async sampai atas
public async Task<string> GetDataAsync()
{
    return await FetchDataAsync();
}

2. ConfigureAwait

// Di library code
public async Task<string> LibraryMethodAsync()
{
    // ConfigureAwait(false) untuk library
    var result = await SomeOperationAsync().ConfigureAwait(false);
    return result;
}

// Di ASP.NET Core, biasanya tidak perlu ConfigureAwait

3. Hindari async void

// ❌ JANGAN - exception tidak bisa di-catch
public async void BadMethod()
{
    await SomethingAsync();
}

// βœ… BENAR - gunakan Task
public async Task GoodMethod()
{
    await SomethingAsync();
}

// Exception: event handlers
button.Click += async (sender, e) =>
{
    await HandleClickAsync();
};

4. ValueTask untuk Hot Paths

// Untuk operasi yang sering synchronous
public ValueTask<int> GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out int value))
    {
        return new ValueTask<int>(value);  // No allocation
    }
    
    return new ValueTask<int>(FetchAndCacheAsync(key));
}

Common Mistakes

Deadlock

// ❌ Deadlock di ASP.NET Framework (tidak di Core)
public ActionResult Index()
{
    var data = GetDataAsync().Result;  // DEADLOCK!
    return View(data);
}

Fire and Forget

// ❌ Exception hilang
public void StartBackgroundWork()
{
    DoWorkAsync();  // Exception tidak akan terlihat!
}

// βœ… Lebih baik
public async Task StartBackgroundWork()
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Background work failed");
    }
}

Ringkasan

SkenarioGunakan
I/O bound (database, HTTP, file)async/await
CPU boundTask.Run() atau parallel
Sequential operationsawait satu per satu
Independent operationsTask.WhenAll()
Race conditionTask.WhenAny()

Async/await adalah salah satu fitur terpenting di C# modern. Kuasai konsep ini untuk membuat aplikasi yang responsif dan scalable!