CHT004: ValueTask.AsTask() called multiple times
Cause
AsTask() is called multiple times on the same ValueTask or ValueTask<T> variable.
Rule Description
Calling AsTask() on a ValueTask consumes it, just like awaiting does. Calling AsTask() multiple times on the same ValueTask is undefined behavior when backed by an IValueTaskSource. The second call may throw InvalidOperationException or return incorrect results.
How to Fix
Option 1: Store the Task result
Store the result of AsTask() in a variable and reuse it:
// Before
ValueTask vt = GetValueTask();
var t1 = vt.AsTask();
var t2 = vt.AsTask(); // Error: already consumed
// After
ValueTask vt = GetValueTask();
Task t = vt.AsTask();
var t1 = t;
var t2 = t; // OK - reusing the same Task
Option 2: Use Preserve()
Use the built-in Preserve() method:
ValueTask vt = GetValueTask();
ValueTask preserved = vt.Preserve();
// Now safe to consume multiple times
await preserved;
await preserved;
Option 3: Await directly
If you don't need a Task, just await the ValueTask:
// Before
ValueTask vt = GetValueTask();
await vt.AsTask();
await vt.AsTask(); // Error
// After
await GetValueTask();
await GetValueTask(); // OK - separate ValueTask instances
When to Suppress
Never suppress this diagnostic. Calling AsTask() multiple times on the same ValueTask is always a bug.
Example
Violating Code
public async Task ProcessAsync()
{
ValueTask vt = DoWorkAsync();
// Pass to multiple consumers
await Task.WhenAll(
vt.AsTask(), // First consumption
vt.AsTask() // CHT004: AsTask() already called
);
}
Fixed Code
public async Task ProcessAsync()
{
// Option 1: Store the Task
Task t = DoWorkAsync().AsTask();
await Task.WhenAll(t, t); // OK - same Task reference
// Option 2: Create separate ValueTasks
await Task.WhenAll(
DoWorkAsync().AsTask(),
DoWorkAsync().AsTask() // OK - different ValueTask instances
);
}