How it works
ValueTask<T> can represent a result available immediately or an operation that completes later. ReadCached returns an already available integer without needing this example to create a new Task<int> for that result. The completed-successfully check makes the synchronous case visible before the value is consumed.
AsTask converts the ValueTask into a Task for a consumer that needs the familiar task-sharing contract. The sample then awaits that task and prints twelve. Conversion can involve allocation, so it should serve an actual caller requirement rather than being applied mechanically to every ValueTask.
A general ValueTask should normally be consumed once. Depending on its backing implementation, repeatedly awaiting it, storing it for arbitrary reuse, or using several consumers can violate its contract. This particular directly stored value is simple, but API callers should not generalize that implementation detail to every ValueTask-returning method.
Returning ValueTask is not an automatic optimization for every asynchronous method. It makes the API contract more complex and may provide little benefit outside a measured hot path with frequent synchronous completion. A Task-returning method is often the clearer default when sharing, caching, or repeated observation is useful.
Practice by implementing a method with a direct cached result branch and a slower async branch that awaits Task.Yield before returning a value. Consume each returned ValueTask exactly once. If a caller needs to share one operation between two awaits, convert it once and retain the resulting Task.
Finally, write down what the producer guarantees and what the caller needs. Does the value complete synchronously often enough to matter? Must several callers observe the same result? Is the operation stored beyond the immediate call? Those questions lead to a better return-type choice than assuming a newer-looking type is always faster or more appropriate.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
ValueTask<int> cached = ReadCached();
Console.WriteLine(cached.IsCompletedSuccessfully);
Task<int> shared = cached.AsTask();
Console.WriteLine(await shared);
static ValueTask<int> ReadCached() => new ValueTask<int>(12);
Expected output
True 12
Consume a ValueTask once unless the producing API explicitly guarantees a stronger contract.