Table of Contents

CHT004: ValueTask.AsTask() called multiple times

Cause

AsTask() is called multiple times on the same ValueTask or ValueTask<T> variable.

Rule Description

Calling AsTask() on a ValueTask consumes it, just like awaiting does. Calling AsTask() multiple times on the same ValueTask is undefined behavior when backed by an IValueTaskSource. The second call may throw InvalidOperationException or return incorrect results.

How to Fix

Option 1: Store the Task result

Store the result of AsTask() in a variable and reuse it:

// Before
ValueTask vt = GetValueTask();
var t1 = vt.AsTask();
var t2 = vt.AsTask(); // Error: already consumed

// After
ValueTask vt = GetValueTask();
Task t = vt.AsTask();
var t1 = t;
var t2 = t; // OK - reusing the same Task

Option 2: Use Preserve()

Use the built-in Preserve() method:

ValueTask vt = GetValueTask();
ValueTask preserved = vt.Preserve();

// Now safe to consume multiple times
await preserved;
await preserved;

Option 3: Await directly

If you don't need a Task, just await the ValueTask:

// Before
ValueTask vt = GetValueTask();
await vt.AsTask();
await vt.AsTask(); // Error

// After
await GetValueTask();
await GetValueTask(); // OK - separate ValueTask instances

When to Suppress

Never suppress this diagnostic. Calling AsTask() multiple times on the same ValueTask is always a bug.

Example

Violating Code

public async Task ProcessAsync()
{
    ValueTask vt = DoWorkAsync();
    
    // Pass to multiple consumers
    await Task.WhenAll(
        vt.AsTask(),  // First consumption
        vt.AsTask()   // CHT004: AsTask() already called
    );
}

Fixed Code

public async Task ProcessAsync()
{
    // Option 1: Store the Task
    Task t = DoWorkAsync().AsTask();
    await Task.WhenAll(t, t); // OK - same Task reference
    
    // Option 2: Create separate ValueTasks
    await Task.WhenAll(
        DoWorkAsync().AsTask(),
        DoWorkAsync().AsTask()  // OK - different ValueTask instances
    );
}

See Also