CHT007: AsTask() stored before signaling may cause performance degradation
Cause
The result of AsTask() is stored in a variable before the underlying async operation completes.
Rule Description
When RunContinuationsAsynchronously is true (the default for CryptoHives.Foundation.Threading primitives), storing the result of AsTask() before the operation signals completion can cause severe performance degradation—often 10x to 100x slower.
This occurs because:
ManualResetValueTaskSourceCoreinternally queues continuations- Storing a
Taskreference before signaling prevents synchronous completion - Forces asynchronous scheduling to the thread pool even when the event is already signaled
- Adds memory allocations for the Task wrapper and continuation state
How to Fix
Option 1: Await ValueTask directly (Recommended)
// Before (slow)
Task t = asyncEvent.WaitAsync().AsTask();
asyncEvent.Set();
await t;
// After (fast)
var waitTask = asyncEvent.WaitAsync();
asyncEvent.Set();
await waitTask;
Option 2: Convert to Task immediately before awaiting
// Before (slow)
Task t = asyncEvent.WaitAsync().AsTask(); // Stored before Set()
DoSomeWork();
asyncEvent.Set();
await t;
// After (faster)
var vt = asyncEvent.WaitAsync();
DoSomeWork();
asyncEvent.Set();
await vt.AsTask(); // Convert immediately before await
Option 3: Store ValueTask, not Task
// Before (slow)
Task t = asyncEvent.WaitAsync().AsTask();
asyncEvent.Set();
await t;
// After (fast)
ValueTask vt = asyncEvent.WaitAsync();
asyncEvent.Set();
await vt;
When to Suppress
Suppress if:
- You understand the performance implications and they are acceptable
- You need a
Taskreference for API compatibility reasons RunContinuationsAsynchronouslyis set tofalse
#pragma warning disable CHT007
Task t = asyncEvent.WaitAsync().AsTask();
#pragma warning restore CHT007
Example
Violating Code
public async Task WaitForSignalAsync()
{
// CHT007: Storing AsTask() result before signal
Task t = _asyncEvent.WaitAsync().AsTask();
// Some other work...
PrepareData();
// Signal the event
_asyncEvent.Set();
// Wait for completion (10-100x slower!)
await t;
}
Fixed Code
public async Task WaitForSignalAsync()
{
// Store ValueTask instead
ValueTask vt = _asyncEvent.WaitAsync();
// Some other work...
PrepareData();
// Signal the event
_asyncEvent.Set();
// Wait for completion (optimal performance)
await vt;
}
Performance Impact
Benchmark comparison (typical results):
| Pattern | Time | Allocations |
|---|---|---|
await valueTask |
~50 ns | 0 B |
await valueTask.AsTask() (immediate) |
~100 ns | 96 B |
Task t = vt.AsTask(); ... await t (stored) |
~5,000 ns | 200+ B |
The stored pattern can be 50-100x slower due to forced async scheduling.
Technical Details
The performance degradation occurs because:
ManualResetValueTaskSourceCore<T>uses a continuation field- When
AsTask()is called, it registers a continuation to complete the Task - If
RunContinuationsAsynchronously=true, the continuation must be queued - If the Task already exists when
SetResult()is called, synchronous completion is impossible - The continuation is forced through
ThreadPool.QueueUserWorkItem