ValueTask und Vermeidung von Allokationen
Verwenden Sie ValueTask, um Heap-Allokationen in Hot Paths zu reduzieren, verstehen Sie die Einschränkungen und vermeiden Sie häufige Fehlanwendungen.
ValueTask und Vermeidung von Allokationen ist eine kostenlose C# Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des C# Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Das Allokationsproblem von Task
Task<T> ist ein Referenztyp — jede async-Methode allokiert einen Task auf dem Heap, selbst wenn das Ergebnis synchron verfügbar ist (etwa aus einem Cache). In häufig ausgeführten Codepfaden kann dies erheblichen GC-Druck verursachen. ValueTask<T> löst dieses Problem.
Was ist ValueTask?
ValueTask<T> ist ein Struct, das entweder ein synchron verfügbares Ergebnis oder einen ausstehenden asynchronen Vorgang darstellen kann. Wenn das Ergebnis synchron verfügbar ist, ist keine Heap-Allokation erforderlich.
// 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 neededValueTask aus einem Interface zurückgeben
Verwenden Sie ValueTask<T> in häufig aufgerufenen Interface-Methoden, die oft synchron abgeschlossen werden. Konsumenten verwenden es genauso wie 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 mit IValueTaskSource
Für maximale Performance implementieren Sie IValueTaskSource<T>, um das zugrunde liegende Zustandsobjekt zu poolen. Dadurch werden selbst bei asynchronen Abschlüssen alle Allokationen vermieden — intern wird dies vom Socket- und I/O-Code von .NET verwendet.
// 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 throughputEinschränkungen von ValueTask: nur einmal awaiten
Wichtige Regel: Ein ValueTask<T> darf höchstens einmal awaitet werden, und Sie dürfen .Result nicht aufrufen, bevor der Vorgang abgeschlossen ist. Ein Verstoß führt zu undefiniertem Verhalten.
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 timesEntscheidungshilfe: ValueTask<T> vs. Task<T>
Verwenden Sie ValueTask<T> nur, wenn das Profiling einen durch asynchrone Methoden verursachten Allokationsdruck zeigt. Verwenden Sie es nicht überall — in selten ausgeführtem Code erhöht es die Komplexität ohne Nutzen.
// 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 itNicht-generisches ValueTask
ValueTask (nicht generisch) eignet sich für Methoden, die Task zurückgeben (asynchron und ohne Rückgabewert). Verwenden Sie es für häufig aufgerufene Methoden, die oft synchron abgeschlossen werden.
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
}
}Allokationen mit BenchmarkDotNet messen
Verwenden Sie [MemoryDiagnoser] in BenchmarkDotNet, um Allokationen pro Vorgang zu messen. Nur so lässt sich zuverlässig überprüfen, ob ValueTask tatsächlich hilft.
[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 in SocketsHttpHandler
Der .NET-HTTP-Stack verwendet intern umfassend ValueTask. Dieses Verständnis hilft Ihnen, performante HTTP-Clients zu schreiben, die zur Allokationsstrategie der Runtime passen.
// 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/OPraxisbeispiel: Cache-Service für häufig ausgeführte Codepfade
Ein Cache-Service, bei dem jeder Aufruf möglicherweise ein Cache-Treffer ist — ValueTask beseitigt Allokationen im häufig ausgeführten Codepfad und unterstützt zugleich einen asynchronen Fallback für Cache-Fehltreffer.
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;
}
}Kurzer Test
Welche wichtige Regel gilt für das Awaiten eines ValueTask<T>?
Zusammenfassung: ValueTask und das Vermeiden von Allokationen
Wichtigste Erkenntnisse:
ValueTask<T>: Struct, das Heap-Allokationen vermeidet, wenn das Ergebnis synchron verfügbar ist- Am besten für häufig ausgeführte Codepfade geeignet, die oft synchron abgeschlossen werden (Cache-Treffer)
- Awaiten Sie ein
ValueTask<T>NIEMALS mehr als einmal — verwenden Sie.AsTask(), um es gemeinsam zu nutzen - Verwenden Sie
[MemoryDiagnoser], um die Einsparungen bei Allokationen vor der Optimierung zu überprüfen - Bevorzugen Sie
Task<T>für die allgemeine Verwendung; setzen SieValueTask<T>nur ein, wenn das Profiling dies rechtfertigt
Häufig gestellte Fragen
Ist die Lektion „ValueTask und Vermeidung von Allokationen“ kostenlos?
Ja — der vollständige Text von „ValueTask und Vermeidung von Allokationen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des C# Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „ValueTask und Vermeidung von Allokationen“?
Verwenden Sie ValueTask, um Heap-Allokationen in Hot Paths zu reduzieren, verstehen Sie die Einschränkungen und vermeiden Sie häufige Fehlanwendungen. Du übst C# Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um C# Academy zu starten?
Keine Vorkenntnisse erforderlich. C# Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „ValueTask und Vermeidung von Allokationen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser C# Academy-Lektion Code schreiben und ausführen?
Ja. Jede C# Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- IAsyncEnumerable und await foreach
- System.Threading.Channels
- ValueTask und Vermeidung von Allokationen
- ConfigureAwait und SynchronizationContext