如何将返回非泛型Task的函数转为ValueTask?非泛型ValueTask问题解析
ValueTask, and How to Convert Non-Generic Task to ValueTask Great question! Let’s break this down into two clear parts to address both your concerns.
Why isn’t there a non-generic ValueTask in System.Threading.Tasks.Extensions?
The core purpose of ValueTask<T> is to eliminate the overhead of allocating a Task<T> object when an asynchronous operation completes synchronously. For operations that don’t return a value, the .NET team realized we don’t need a separate non-generic type—instead, ValueTask<Unit> (where Unit is a tiny, empty struct representing "no value") achieves the exact same goal with almost zero additional cost.
When the System.Threading.Tasks.Extensions package was first released (primarily for older frameworks like .NET Framework), the focus was on solving the most common asynchronous scenario: operations that return a value. Adding a non-generic ValueTask would have added extra complexity without meaningful gains, since ValueTask<Unit> works perfectly for void-like async workflows.
That said, newer .NET versions (Core 3.0+) do include a built-in non-generic ValueTask type. It wasn’t included in the original package because the tradeoff of adding it didn’t make sense at the time.
How to convert a non-generic Task-returning function to use ValueTask?
Since you’re working with the System.Threading.Tasks.Extensions package (which only includes ValueTask<T>), you’ll use ValueTask<Unit> to represent void-like asynchronous operations. Here are a few practical approaches:
Simple wrap with async/await (great for readability, minimal overhead):
using System.Threading.Tasks; using System.Threading.Tasks.Extensions; // For ValueTask<T> using System.Runtime.CompilerServices; // For the Unit struct async ValueTask<Unit> MyValueTaskWrapperAsync() { await MyVoidTaskMethodAsync(); // Your existing non-generic Task method return Unit.Value; }The
Unitstruct is lightweight (no fields), so this adds barely any overhead to your code.Performance-optimized wrap (avoid async/await allocation for synchronous cases):
If you don’t need to add extra logic around the original task, you can directly create aValueTask<Unit>and optimize for synchronous completion:ValueTask<Unit> MyValueTaskWrapperAsync() { Task underlyingTask = MyVoidTaskMethodAsync(); return underlyingTask.IsCompletedSuccessfully ? ValueTask.FromResult(Unit.Value) : new ValueTask<Unit>(underlyingTask, Unit.Value); }This checks if the task is already done and returns a pre-allocated completed
ValueTask<Unit>to skip unnecessary allocations.If targeting .NET Core 3.0+ (even with the package):
You can use the built-in non-genericValueTasktype, which has a constructor that accepts a non-genericTask:ValueTask MyValueTaskWrapperAsync() { return new ValueTask(MyVoidTaskMethodAsync()); }For synchronous completion, you can also return
ValueTask.CompletedTaskinstead of creating a new instance.
Critical Note
When working with any ValueTask (generic or non-generic), never await it multiple times or store it for later use. Unlike Task, ValueTask can represent either a completed value or a pending operation—reusing it can lead to unexpected behavior or performance issues.
内容的提问来源于stack exchange,提问作者Benjamin Hodgson

