ValueTask<TResult> 和异步状态机

The ValueTask<TResult> and the async state machine

根据 documentation ValueTask<TResult>...

Provides a value type that wraps a Task<TResult> and a TResult, only one of which is used.

我的问题是关于 C# 编译器在遇到 async 关键字时生成的状态机。当结果立即可用时生成一个包含 TResultValueTask<TResult> 或当结果在 await 之后生成一个包含 Task<TResult>ValueTask<TResult> 是否足够聪明? ?这是一个例子:

static async ValueTask<DateTime> GetNowAsync(bool withDelay)
{
    if (withDelay) await Task.Delay(1000);
    return DateTime.Now;
}

static void Test()
{
    var t1 = GetNowAsync(false);
    var t2 = GetNowAsync(true);
}

调用 GetNowAsync(false) 应该 return 一个 TResult 包装器,因为没有等待,调用 GetNowAsync(true) 应该 return 一个 Task<TResult> 包装器,因为在结果可用之前等待 Task.Delay。我担心状态机总是 returns Task 包装器的可能性,使 ValueTask 类型相对于 Task 类型的所有优点都无效(并保留所有缺点) .据我所知,类型 ValueTask<TResult> 的属性没有提供关于它内部包装的内容的指示。我将上面的代码粘贴到 sharplab.io,但输出也没有帮助我回答这个问题。

编译器很笨,可以按照它的指示去做:

https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs,409

[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))]
[StructLayout(LayoutKind.Auto)]
public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>>

小心使用ValueTask:

我想我应该回答我自己的问题,因为我现在知道答案了。答案是我的担心是没有根据的:C# 编译器足够聪明 可以在每种情况下发出正确类型的 ValueTask<TResult>。当结果同步可用时,它会发出一个值包装器,否则会发出一个任务包装器。

我通过性能测量得出这个结论:通过测量每种情况下分配的内存,以及创建相同数量的任务所需的时间。结果清晰且一致。例如,ValueTask<int> 在包装 int 值时恰好消耗 12 个字节,而在包装 Task<int> 时恰好消耗 48 个字节,因此毫无疑问幕后发生了什么。