7

根据文件ValueTask<TResult>... _

提供包装 aTask<TResult>和 a的值类型TResult,仅使用其中一个。

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

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)应该返回一个TResult包装器,因为没有等待,调用GetNowAsync(true)应该返回一个Task<TResult>包装器,因为Task.Delay在结果可用之前等待 a。我担心状态机总是返回Task包装器的可能性,从而抵消了该ValueTask类型的所有优点Task(并保留了所有缺点)。据我所知,该类型的属性ValueTask<TResult>没有提供关于它内部包装的指示。我将上面的代码粘贴到了sharplab.io,但输出也没有帮助我回答这个问题。

4

2 回答 2

3

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

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

于 2019-08-23T16:15:29.373 回答
-1

编译器足够愚蠢,可以按照它的指示去做:

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

于 2019-08-10T15:36:57.837 回答