ValueTask et réduction des allocations
Utilisez ValueTask pour réduire les allocations sur le tas dans les chemins critiques, comprenez ses contraintes et évitez les erreurs d’utilisation courantes.
ValueTask et réduction des allocations est une leçon C# Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C# Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C# Academy comprend 4 leçons au total.
Le problème d’allocation avec Task
Task<T> est un type référence : chaque méthode async alloue une Task sur le tas, même si le résultat est disponible de manière synchrone (par exemple, depuis un cache). Dans les chemins d’exécution fréquents, cela peut exercer une pression importante sur le GC. ValueTask<T> résout ce problème.
Qu’est-ce que ValueTask ?
ValueTask<T> est une structure qui peut représenter soit un résultat disponible de manière synchrone, soit une opération asynchrone en attente. Lorsque le résultat est synchrone, aucune allocation sur le tas n’est nécessaire.
// 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 neededRenvoyer ValueTask depuis une interface
Utilisez ValueTask<T> dans les méthodes d’interface qui sont appelées fréquemment et se terminent souvent de manière synchrone. Les consommateurs l’utilisent exactement comme 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 avec IValueTaskSource
Pour des performances extrêmes, implémentez IValueTaskSource<T> afin de mettre en pool l’objet d’état sous-jacent. Cela évite toutes les allocations, même pour les achèvements asynchrones ; cette technique est utilisée en interne par le code réseau et d’E/S de .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 throughputContraintes de ValueTask : un seul await
Règle critique : un ValueTask<T> doit être attendu au maximum une fois, et vous ne pouvez pas appeler .Result avant qu’il ne soit terminé. Le non-respect de cette règle entraîne un comportement indéfini.
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 timesGuide de décision : ValueTask<T> ou Task<T>
Utilisez ValueTask<T> uniquement lorsque le profilage révèle une pression due aux allocations des méthodes asynchrones. Ne l’utilisez pas partout : il ajoute de la complexité sans avantage dans le code peu fréquent.
// 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 itValueTask non générique
ValueTask (non générique) convient aux méthodes renvoyant Task (asynchrones sans valeur de retour). Utilisez-le pour les méthodes fréquemment appelées qui se terminent souvent de manière synchrone.
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
}
}Mesurer les allocations avec BenchmarkDotNet
Utilisez [MemoryDiagnoser] dans BenchmarkDotNet pour mesurer les allocations par opération. C’est la seule manière fiable de vérifier que ValueTask apporte réellement un bénéfice.
[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 hitValueTask dans SocketsHttpHandler
La pile HTTP de .NET utilise largement ValueTask en interne. Comprendre ce fonctionnement vous aide à écrire des clients HTTP hautes performances qui respectent la stratégie d’allocation du moteur d’exécution.
// 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/OCas réel : service de cache sur un chemin d’exécution fréquent
Un service de cache dans lequel chaque appel peut être un succès de cache : ValueTask élimine les allocations sur le chemin fréquent tout en prenant en charge le repli asynchrone en cas d’échec du 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;
}
}Vérification rapide
Quelle est la règle critique concernant l’attente d’un ValueTask
Récapitulatif : ValueTask et prévention des allocations
Points clés à retenir :
ValueTask<T>: structure qui évite l’allocation sur le tas lorsque le résultat est synchrone- Idéal pour les chemins d’exécution fréquents qui se terminent souvent de manière synchrone (succès de cache)
- N’attendez JAMAIS un
ValueTask<T>plus d’une fois — utilisez.AsTask()pour le partager - Utilisez
[MemoryDiagnoser]pour vérifier les économies d’allocation avant d’optimiser - Préférez
Task<T>pour l’usage général ; utilisezValueTask<T>uniquement lorsque le profilage le justifie
Questions Fréquemment Posées
La leçon « ValueTask et réduction des allocations » est-elle gratuite ?
Oui — le texte complet de « ValueTask et réduction des allocations » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C# Academy, passe à CoddyKit PRO. Le cours C# Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « ValueTask et réduction des allocations » ?
Utilisez ValueTask pour réduire les allocations sur le tas dans les chemins critiques, comprenez ses contraintes et évitez les erreurs d’utilisation courantes. Tu pratiques C# Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer C# Academy ?
Aucune expérience préalable n'est requise. C# Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « ValueTask et réduction des allocations » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon C# Academy ?
Oui. Chaque leçon C# Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- IAsyncEnumerable et await foreach
- System.Threading.Channels
- ValueTask et réduction des allocations
- ConfigureAwait et contexte de synchronisation