Table of Contents

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:

  1. ManualResetValueTaskSourceCore internally queues continuations
  2. Storing a Task reference before signaling prevents synchronous completion
  3. Forces asynchronous scheduling to the thread pool even when the event is already signaled
  4. Adds memory allocations for the Task wrapper and continuation state

How to Fix

// 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:

  1. You understand the performance implications and they are acceptable
  2. You need a Task reference for API compatibility reasons
  3. RunContinuationsAsynchronously is set to false
#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:

  1. ManualResetValueTaskSourceCore<T> uses a continuation field
  2. When AsTask() is called, it registers a continuation to complete the Task
  3. If RunContinuationsAsynchronously=true, the continuation must be queued
  4. If the Task already exists when SetResult() is called, synchronous completion is impossible
  5. The continuation is forced through ThreadPool.QueueUserWorkItem

See Also