Table of Contents

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:

  1. Task.WhenAll / Task.WhenAny: These methods expect Task arguments and may access them multiple times internally
  2. Custom methods: Methods that store or re-use their arguments may cause double consumption
  3. Implicit conversions: Some APIs may implicitly convert ValueTask in 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.WhenAll
  • Task.WhenAny
  • ValueTask.WhenAll (if available)
  • ValueTask.WhenAny (if available)

See Also