C# Academy · Aula

ValueTask e como evitar alocações

Use ValueTask para reduzir alocações na memória heap em caminhos críticos, entenda suas restrições e evite erros comuns de uso.

Aula 3 de 412 etapas

ValueTask e como evitar alocações é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.

O Problema de Alocação com Task

Task<T> é um tipo de referência — todo método async aloca uma Task no heap, mesmo quando o resultado está disponível de forma síncrona (por exemplo, vindo de um cache). Em caminhos críticos, isso pode causar uma pressão significativa sobre o coletor de lixo. ValueTask<T> resolve esse problema.

O Que é ValueTask?

ValueTask<T> é uma estrutura que pode representar tanto um resultado disponível de forma síncrona quanto uma operação assíncrona pendente. Quando o resultado é síncrono, não é necessária nenhuma alocação no heap.

// 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

Retornando ValueTask de uma Interface

Use ValueTask<T> em métodos de interface que sejam chamados com frequência e geralmente sejam concluídos de forma síncrona. Os consumidores usam o resultado exatamente como usariam 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 com IValueTaskSource

Para obter desempenho extremo, implemente IValueTaskSource<T> para reutilizar o objeto de estado de suporte. Isso evita todas as alocações, inclusive para conclusões assíncronas — é usado internamente pelo código de soquetes e E/S do .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

Restrições de ValueTask: Aguarde Apenas Uma Vez

Regra crítica: um ValueTask<T> deve ser aguardado no máximo uma vez, e você não pode chamar .Result antes que ele seja concluído. Violar essa regra causa comportamento indefinido.

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

Guia de Decisão: ValueTask<T> vs. Task<T>

Use ValueTask<T> somente quando a criação de perfis mostrar pressão de alocação causada por métodos assíncronos. Não o use em todo lugar — ele acrescenta complexidade sem trazer benefícios em códigos executados com pouca frequência.

// 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 Não Genérico

ValueTask (não genérico) funciona para métodos que retornam Task (assíncronos sem valor). Use-o em métodos chamados com frequência que geralmente são concluídos de forma síncrona.

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
    }
}

Medindo Alocações com BenchmarkDotNet

Use [MemoryDiagnoser] no BenchmarkDotNet para medir as alocações por operação. Essa é a única maneira confiável de verificar se ValueTask realmente está ajudando.

[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 em SocketsHttpHandler

A pilha HTTP do .NET usa ValueTask extensivamente de forma interna. Compreender isso ajuda você a escrever clientes HTTP de alto desempenho alinhados à estratégia de alocação do ambiente de execução.

// 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

Na Prática: Serviço de Cache em Caminho Crítico

Um serviço de cache em que cada chamada pode ser um acerto no cache — ValueTask elimina as alocações no caminho crítico e ainda oferece suporte ao fallback assíncrono para falhas de 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;
    }
}

Verificação Rápida

Qual é a regra crítica sobre aguardar um ValueTask?

Recapitulação: ValueTask e Como Evitar Alocações

Principais conclusões:

  • ValueTask<T>: estrutura que evita alocações no heap quando o resultado é síncrono
  • Ideal para caminhos críticos que são concluídos com frequência de forma síncrona (acertos de cache)
  • NEVER aguarde um ValueTask<T> mais de uma vez — use .AsTask() para compartilhá-lo
  • Use [MemoryDiagnoser] para verificar a economia de alocações antes de otimizar
  • Prefira Task<T> para uso geral; use ValueTask<T> somente quando a criação de perfis justificar
Grátis para começar

Aprenda C# com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
93
Aulas
346

Perguntas Frequentes

A aula “ValueTask e como evitar alocações” é grátis?

Sim — o texto completo de “ValueTask e como evitar alocações” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.

O que vou aprender em “ValueTask e como evitar alocações”?

Use ValueTask para reduzir alocações na memória heap em caminhos críticos, entenda suas restrições e evite erros comuns de uso. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar C# Academy?

Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “ValueTask e como evitar alocações”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de C# Academy?

Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. IAsyncEnumerable e await foreach
  2. System.Threading.Channels
  3. ValueTask e como evitar alocações
  4. ConfigureAwait e contexto de sincronização
← Voltar para C# Academy