假设我有一个像这样的方法签名:
public async ValueTask<int> GetSomeValueAsync(int number);
在内部,它可能会进行异步 HTTP 调用,但它会缓存结果,因此下次使用相同的
number
调用它时,它可以从缓存中返回值,而无需异步调用。因此它返回一个 ValueTask
,因为这避免了为同步执行路径创建 Task
的开销。
我的问题是,这个方法的调用者,如果它没有进行其他异步调用,签名应该是
Task
还是应该 ValueTask
一直传播到调用堆栈?
例如应该是这样吗:
public async Task<int> CallingMethodAsync(int number)
{
return await GetSomeValueAsync(number) + 1;
}
或者应该是:
public async ValueTask<int> CallingMethodAsync(int number)
{
return await GetSomeValueAsync(number) + 1;
}
(上面的例子经过简化以说明这一点)
假设
GetSomeValueAsync
预计在大多数情况下同步完成,并且 CallingMethodAsync
不 await
除了 GetSomeValueAsync
之外的任何其他内容,并且您的目标是最小化内存分配,那么ValueTask<TResult>
更佳。
public async ValueTask<int> CallingMethodAsync(int number)
{
return await GetSomeValueAsync(number) + 1;
}
这是因为当
ValueTask<TResult>
在创建时已经完成时,它会在内部包装 TResult
,而不是 Task<TResult>
,因此它不会分配任何内容。相反,如果返回 Task<TResult>
,则始终必须创建一个新对象,除非它恰好是 .NET 运行时本身内部缓存的少数类型+值组合之一,例如 Task<int>
值从 -1 到 9(请参阅内部静态 TaskCache
类)。