CHT008: ValueTask not awaited or consumed
Cause
A ValueTask or ValueTask<T> is returned from a method call but is never awaited, stored, or otherwise consumed.
Rule Description
When a ValueTask is returned but not consumed:
- Resource leaks: When backed by pooled
IValueTaskSource, the pooled object is never returned to the pool - Silent failures: Exceptions thrown by the async operation are lost
- Unintended behavior: The operation may not complete as expected
- Pool exhaustion: Repeated leaks can exhaust the object pool
This is particularly important for CryptoHives.Foundation.Threading primitives which use pooled IValueTaskSource implementations.
How to Fix
Option 1: Await the ValueTask
// Before
GetValueTask(); // Warning: not consumed
// After
await GetValueTask();
Option 2: Store for later consumption
// Before
GetValueTask(); // Warning
// After
ValueTask vt = GetValueTask();
// ... later ...
await vt;
Option 3: Explicitly discard
If you intentionally don't want to await:
// Before
GetValueTask(); // Warning
// After
_ = GetValueTask(); // Explicit discard - warning suppressed
Option 4: Use discard with await for fire-and-forget
// Fire and forget with exception handling
_ = Task.Run(async () =>
{
try
{
await GetValueTask();
}
catch (Exception ex)
{
// Log the exception
}
});
When to Suppress
Only suppress if you understand the consequences:
#pragma warning disable CHT008
GetValueTask(); // Intentionally not awaited
#pragma warning restore CHT008
However, prefer explicit discard (_ =) over suppression as it makes intent clear.
Example
Violating Code
public void ProcessData()
{
// CHT008: ValueTask not consumed
_asyncEvent.WaitAsync();
// CHT008: ValueTask not consumed
GetDataAsync();
DoSomethingElse();
}
Fixed Code
public async Task ProcessDataAsync()
{
// Await the operations
await _asyncEvent.WaitAsync();
var data = await GetDataAsync();
DoSomethingElse();
}
// Or if fire-and-forget is intentional
public void ProcessData()
{
// Explicit discard shows intent
_ = _asyncEvent.WaitAsync();
_ = GetDataAsync();
DoSomethingElse();
}
Impact on Pooled Primitives
When using CryptoHives.Foundation.Threading async primitives:
var asyncEvent = new AsyncAutoResetEvent();
// BAD: Pooled IValueTaskSource is never returned to pool
asyncEvent.WaitAsync(); // CHT008
// GOOD: IValueTaskSource is returned after await
await asyncEvent.WaitAsync();
Each unconsumed ValueTask from these primitives:
- Leaks a pooled
IValueTaskSourceinstance - May eventually exhaust the pool under high load
- Causes increased memory allocation as new instances are created
Fire-and-Forget Pattern
If you need fire-and-forget behavior, handle it explicitly:
public static class FireAndForget
{
public static async void SafeFireAndForget(
this ValueTask valueTask,
Action<Exception>? onException = null)
{
try
{
await valueTask.ConfigureAwait(false);
}
catch (Exception ex)
{
onException?.Invoke(ex);
}
}
}
// Usage
GetValueTask().SafeFireAndForget(ex => _logger.LogError(ex, "Operation failed"));