CHT010: ValueTask captured in lambda/closure
Cause
A ValueTask or ValueTask<T> variable is captured from an outer scope into a lambda expression or a local function.
Rule Description
A ValueTask is a struct that can only be consumed once. When a ValueTask is captured in a closure (e.g., a lambda or a local function), it is potentially unsafe for two reasons:
- The closure might be invoked multiple times.
- The
ValueTaskmay have been consumed by other code before the closure executes.
Awaiting a captured ValueTask leads to undefined behavior, and when backed by an IValueTaskSource, the second await may throw InvalidOperationException or return incorrect results.
How to Fix
Option 1: Convert to Task at declaration
If you need to use the result within a closure and ensure it can be accessed safely multiple times (or because the closure might be called multiple times), convert the ValueTask to a Task immediately:
// Before
ValueTask vt = GetValueTask();
var myLambda = async () => {
// This is unsafe because 'vt' is a captured ValueTask
return await vt;
};
// After
Task t = GetValueTask().AsTask();
var myLambda = async () => {
// This is safe because 't' is a Task
return await t;
};
Option 2: Use Preserve()
Use the Preserve() extension method for safe conversion. This is useful if you want to keep the ValueTask behavior but need to ensure it's safe to capture it for multiple consumes.
using CryptoHives.Foundation.Threading;
Task t = GetValueTask().Preserve();
var myLambda = async () => {
// This is safe because .Preserve() allows multiple consumes
return await t;
};
Option 3: Store the result
If you only need the result of the ValueTask, await it once outside of the closure and store the resulting value:
// Before
ValueTask<int> vt = GetValueTask();
var myLambda = async () => {
// This is unsafe
return await vt;
};
// After
int result = await GetValueTask().ConfigureAwait(false);
var myLambda = () => {
// This is safe
return result;
};
When to Suppress
Never suppress this diagnostic. Capturing a ValueTask in a closure without explicit preservation or conversion to a Task is a high-risk pattern that leads to undefined behavior.
Example
Violating Code
public async Task ProcessAsync()
{
ValueTask vt = DoWorkAsync();
// Captured vt in lambda
var action = async () => {
await vt; // CHT010: vt is captured and potentially already consumed
};
await action();
}
Fixed Code
public async Task ProcessAsync()
{
Task t = DoWorkAsync().AsTask();
// Captured t in lambda
var action = async () => {
await t; // OK - Task can be safely awaited multiple times
};
await action();
}