CHT006: ValueTask passed to potentially unsafe method
Cause
A ValueTask or ValueTask<T> is passed to a method that may consume it multiple times, such as Task.WhenAll or Task.WhenAny.
Rule Description
Certain methods are designed to work with Task and may consume their arguments multiple times or in unexpected ways. Passing a ValueTask directly to these methods is dangerous because:
Task.WhenAll/Task.WhenAny: These methods expectTaskarguments and may access them multiple times internally- Custom methods: Methods that store or re-use their arguments may cause double consumption
- Implicit conversions: Some APIs may implicitly convert
ValueTaskin ways that consume it
How to Fix
Option 1: Convert to Task with AsTask()
// Before
await Task.WhenAll(GetValueTask()); // Warning
// After
await Task.WhenAll(GetValueTask().AsTask()); // OK
Option 2: Use Preserve()
// Before
ValueTask vt = GetValueTask();
await SomeMethod(vt); // Warning if SomeMethod may re-consume
// After
ValueTask vt = GetValueTask();
await SomeMethod(vt.Preserve()); // OK - can be consumed multiple times
Option 3: Await separately
// Before
await Task.WhenAll(
GetValueTask1(), // Warning
GetValueTask2() // Warning
);
// After
var t1 = GetValueTask1().AsTask();
var t2 = GetValueTask2().AsTask();
await Task.WhenAll(t1, t2);
When to Suppress
Suppress if you know the target method only consumes its argument once:
// If ProcessAsync is known to only await once
#pragma warning disable CHT006
await ProcessAsync(GetValueTask());
#pragma warning restore CHT006
Example
Violating Code
public async Task ProcessMultipleAsync()
{
// Passing ValueTask to WhenAll
await Task.WhenAll(
GetDataAsync(), // CHT006
GetDataAsync() // CHT006
);
}
public async Task ProcessWithTimeoutAsync()
{
// Passing ValueTask to WhenAny
await Task.WhenAny(
GetDataAsync(), // CHT006
Task.Delay(TimeSpan.FromSeconds(5))
);
}
Fixed Code
public async Task ProcessMultipleAsync()
{
// Convert to Task first
await Task.WhenAll(
GetDataAsync().AsTask(),
GetDataAsync().AsTask()
);
}
public async Task ProcessWithTimeoutAsync()
{
// Convert to Task for WhenAny
var dataTask = GetDataAsync().AsTask();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
await Task.WhenAny(dataTask, timeoutTask);
}
Detected Methods
The analyzer currently detects passing ValueTask to:
Task.WhenAllTask.WhenAnyValueTask.WhenAll(if available)ValueTask.WhenAny(if available)