0Pricing
C# Academy · Pelajaran

ValueTask & Menghindari Alokasi

Gunakan ValueTask untuk mengurangi alokasi heap pada jalur yang sering dijalankan, pahami batasannya, dan hindari kesalahan penggunaan yang umum.

ValueTask & Menghindari Alokasi adalah pelajaran C# Academy gratis di CoddyKit. Ini adalah pelajaran 3 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar C# Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus C# Academy mencakup 4 pelajaran total.

Masalah Alokasi pada Task

Task<T> adalah tipe referensi — setiap metode async mengalokasikan Task di memori dinamis, bahkan jika hasilnya tersedia secara sinkron (misalnya dari cache). Pada jalur kritis, hal ini dapat menimbulkan beban besar pada pengelolaan memori. ValueTask<T> mengatasi masalah ini.

Apa Itu ValueTask?

ValueTask<T> adalah struktur yang dapat merepresentasikan hasil yang tersedia secara sinkron atau operasi asinkron yang masih tertunda. Jika hasilnya sinkron, tidak diperlukan alokasi memori dinamis.

// Task<T>: always allocates, even when result is cached
public async Task<string> GetCached_Task() =>
    _cache.TryGetValue("key", out var v) ? v : await FetchAsync();

// ValueTask<T>: zero allocation when value is cached
public ValueTask<string> GetCached_ValueTask() =>
    _cache.TryGetValue("key", out var v)
        ? ValueTask.FromResult(v)  // no allocation
        : new ValueTask<string>(FetchAsync()); // allocation only when needed

Mengembalikan ValueTask dari Antarmuka

Gunakan ValueTask<T> dalam metode antarmuka yang sering dipanggil dan sering selesai secara sinkron. Konsumen menggunakannya persis seperti Task<T>.

public interface IProductRepository
{
    // ValueTask: often completes synchronously from cache
    ValueTask<Product?> GetByIdAsync(int id);

    // Task: always async (DB query)
    Task<List<Product>> SearchAsync(string query);
}

// Consumption is identical to Task:
var product = await _repo.GetByIdAsync(42);
var results = await _repo.SearchAsync("keyboard");

ValueTask dengan IValueTaskSource

Untuk kinerja ekstrem, implementasikan IValueTaskSource<T> untuk menggunakan kembali objek keadaan pendukung. Cara ini menghindari semua alokasi, bahkan untuk penyelesaian asinkron — dan digunakan secara internal oleh kode soket dan operasi masukan/keluaran .NET.

// In most cases, use the simple ValueTask approach.
// IValueTaskSource is for library authors needing zero-alloc async at extreme scale.

// Example: System.IO.Pipelines uses this internally
var result = await pipe.Reader.ReadAsync(ct);
// ReadAsync returns ValueTask<ReadResult> backed by IValueTaskSource
// -- zero allocation for every read even under high throughput

Batasan ValueTask: Hanya Boleh Di-await Sekali

Aturan penting: ValueTask<T> harus di-await paling banyak satu kali, dan Anda tidak boleh memanggil .Result sebelum operasi selesai. Pelanggaran terhadap aturan ini menyebabkan perilaku tidak terdefinisi.

var vt = repo.GetByIdAsync(42);

// BAD: await multiple times
var a = await vt; // OK
var b = await vt; // UNDEFINED BEHAVIOR — don't do this!

// BAD: two concurrent awaits
var t1 = vt.AsTask(); // converts to Task (safe to share)
var t2 = vt.AsTask(); // second AsTask() on same ValueTask — WRONG

// GOOD: if you need multiple awaits, convert to Task
var task = repo.GetByIdAsync(42).AsTask();
var r1 = await task;
var r2 = await task; // OK — Task can be awaited multiple times

Panduan Memilih ValueTask<T> atau Task<T>

Gunakan ValueTask<T> hanya jika pembuatan profil menunjukkan beban alokasi dari metode asinkron. Jangan menggunakannya di semua tempat — penggunaan ini menambah kerumitan tanpa manfaat pada kode yang jarang dipanggil.

// USE ValueTask when:
// 1. The method often completes synchronously (cache hit)
// 2. The method is on a hot path called millions of times/sec
// 3. You're writing a library with high-throughput I/O

// USE Task when:
// 1. The result is always async
// 2. The method might be awaited multiple times
// 3. You use Task.WhenAll / Task.WhenAny
// 4. You store the task in a variable and share it

ValueTask Non-Generik

ValueTask (non-generik) digunakan untuk metode yang mengembalikan Task (asinkron tanpa nilai). Gunakan untuk metode yang sering dipanggil dan sering selesai secara sinkron.

public interface ICache
{
    // Often a no-op when value exists — ValueTask avoids allocation
    ValueTask SetAsync(string key, string value);
    ValueTask InvalidateAsync(string key);
}

public class InMemoryCache : ICache
{
    private readonly Dictionary<string, string> _data = new();

    public ValueTask SetAsync(string key, string value)
    {
        _data[key] = value;
        return ValueTask.CompletedTask; // no allocation
    }
}

Mengukur Alokasi dengan BenchmarkDotNet

Gunakan [MemoryDiagnoser] dalam BenchmarkDotNet untuk mengukur alokasi per operasi. Ini adalah satu-satunya cara yang andal untuk memverifikasi bahwa ValueTask benar-benar membantu.

[MemoryDiagnoser]
public class CacheBenchmark
{
    private readonly CachedRepo _repo = new();

    [Benchmark]
    public async Task<Product?> Task_GetById()
        => await _repo.GetByIdTask(1);

    [Benchmark]
    public async ValueTask<Product?> ValueTask_GetById()
        => await _repo.GetByIdValueTask(1);
}

// Run: dotnet run -c Release
// Compare Gen0 allocations column — ValueTask should show 0B on cache hit

ValueTask dalam SocketsHttpHandler

Tumpukan HTTP .NET banyak menggunakan ValueTask secara internal. Memahami hal ini membantu Anda menulis klien HTTP berkinerja tinggi yang selaras dengan strategi alokasi waktu jalan.

// HttpClient uses ValueTask-based sockets internally.
// When using HttpClient.GetAsync(), the response is awaited with Task<HttpResponseMessage>.
// For streaming large bodies, use:
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
// Stream reads internally use ValueTask<int> for zero-allocation I/O

Praktik Nyata: Layanan Cache Jalur Kritis

Layanan cache yang setiap panggilannya berpotensi menemukan data di cache — ValueTask menghilangkan alokasi pada jalur kritis sekaligus tetap mendukung jalur cadangan asinkron saat data tidak ditemukan di cache.

public class ProductCache
{
    private readonly Dictionary<int, Product> _cache = new();
    private readonly IProductRepository _repo;

    public ProductCache(IProductRepository repo) => _repo = repo;

    public ValueTask<Product?> GetAsync(int id)
    {
        // Cache hit: no allocation
        if (_cache.TryGetValue(id, out var cached))
            return ValueTask.FromResult<Product?>(cached);

        // Cache miss: async fetch
        return new ValueTask<Product?>(FetchAndCacheAsync(id));
    }

    private async Task<Product?> FetchAndCacheAsync(int id)
    {
        var product = await _repo.GetByIdAsync(id);
        if (product is not null) _cache[id] = product;
        return product;
    }
}

Pemeriksaan Singkat

Apa aturan penting terkait penggunaan await pada ValueTask?

Ringkasan: ValueTask dan Menghindari Alokasi

Inti penting:

  • ValueTask<T>: struktur yang menghindari alokasi memori dinamis saat hasilnya sinkron
  • Paling cocok untuk jalur kritis yang sering selesai secara sinkron (saat data ditemukan di cache)
  • JANGAN PERNAH melakukan await pada ValueTask<T> lebih dari sekali — gunakan .AsTask() untuk berbagi
  • Gunakan [MemoryDiagnoser] untuk memverifikasi penghematan alokasi sebelum melakukan optimasi
  • Utamakan Task<T> untuk penggunaan umum; gunakan ValueTask<T> hanya jika dibenarkan oleh pembuatan profil

Pertanyaan yang Sering Diajukan

Apakah pelajaran “ValueTask & Menghindari Alokasi” gratis?

Ya — teks lengkap “ValueTask & Menghindari Alokasi” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus C# Academy, upgrade ke CoddyKit PRO. Kursus C# Academy mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “ValueTask & Menghindari Alokasi”?

Gunakan ValueTask untuk mengurangi alokasi heap pada jalur yang sering dijalankan, pahami batasannya, dan hindari kesalahan penggunaan yang umum. Kamu berlatih C# Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai C# Academy?

Tidak diperlukan pengalaman sebelumnya. C# Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 3 dari 4.

Berapa lama pelajaran “ValueTask & Menghindari Alokasi” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran C# Academy ini?

Ya. Setiap pelajaran C# Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. IAsyncEnumerable & await foreach
  2. System.Threading.Channels
  3. ValueTask & Menghindari Alokasi
  4. ConfigureAwait & Konteks Sinkronisasi
← Kembali ke C# Academy