将 ValueTask<T> 转换为非通用 ValueTask
Convert a ValueTask<T> to a non generic ValueTask
这里提出的问题与 相同,旨在为其创建一个明确的解决方案。
最准确的答案是 Stephen Toub 自己在 this issue 中给出的,这正是关于这个问题的。 "recommendended code" 是以下一个:
public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
if (valueTask.IsCompletedSuccessfully)
{
valueTask.GetResult();
return default;
}
return new ValueTask(valueTask.AsTask());
}
此答案不是最新的 - ValueTask 不公开 GetResult()(仅公开结果 属性) - 问题是:
- 是否需要"pull"从ValueTask中取出Result("release"这个ValueTask下可能运行的IValueTaskSource)?
- 如果是:
- 是上面缺少的
.GetAwaiter()
调用吗?
- 或者对 属性 的虚假调用是否保证有效
var fake = valueTask.Result;
?总是? (怕死码淘汰。)
- 如果不是,像下面这样的直接实现是否足够(并且是最佳的)?
public static ValueTask AsNonGenericValueTask<T>( in this ValueTask<T> valueTask )
{
return valueTask.IsCompletedSuccessfully ? default : new ValueTask( valueTask.AsTask() );
}
该代码中缺少的是 .GetAwaiter()
:
public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
if (valueTask.IsCompletedSuccessfully)
{
valueTask.GetAwaiter().GetResult();
return default;
}
return new ValueTask(valueTask.AsTask());
}
你说对了一部分,因为你不关心结果。但是您可能会关心抛出的异常或取消,如果您不查询结果,您会错过这些。
或者你可以这样写:
public static async ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
=> await valueTask;
您可以使用:
valueTask.GetAwaiter().GetResult();
...或者:
_ = valueTask.Result;
两者都委托给基础 IValueTaskSource<T>.GetResult
method, assuming that the ValueTask<T>
is backed by a IValueTaskSource<T>
。使用更短的(第二种)方法应该稍微更有效,因为它涉及更少的方法调用。
您也可以完全忽略获取结果。没有要求必须至少等待一次 ValueTask<T>
,或者必须至少检索一次其结果。收到 ValueTask<T>
然后忘记它是完全有效的。 documented restriction 是:
A ValueTask<TResult>
instance may only be awaited once, [...]
这是“只能”,而不是“必须”。
虽然得到结果仍然是个好主意。通过检索结果,您表示 ValueTask<TResult>
已被消耗,因此可以重用基础 IValueTaskSource<T>
。重用 IValueTaskSource<T>
个实例使基于 ValueTask<T>
的实现更加高效,因为它们分配内存的频率较低。要获得一个想法,请查看内部 System.Threading.Channels.AsyncOperation<TResult>
class. This class implements the IValueTaskSource<TResult>
interface. Here is how the GetResult
的实现:
/// <summary>Gets the result of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
public TResult GetResult(short token)
{
if (_currentId != token)
{
ThrowIncorrectCurrentIdException();
}
if (!IsCompleted)
{
ThrowIncompleteOperationException();
}
ExceptionDispatchInfo? error = _error;
TResult? result = _result;
_currentId++;
if (_pooled)
{
Volatile.Write(ref _continuation, s_availableSentinel); // only after fetching all needed data
}
error?.Throw();
return result!;
}
将私有字段 Action<object> _continuation
设置为静态只读值 s_availableSentinel
允许 AsyncOperation<TResult>
被后续异步操作重用(例如 ChannelReader.ReadAsync
) .否则下一个异步操作将分配一个新的 AsyncOperation<TResult>
实例。
这里提出的问题与
public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
if (valueTask.IsCompletedSuccessfully)
{
valueTask.GetResult();
return default;
}
return new ValueTask(valueTask.AsTask());
}
此答案不是最新的 - ValueTask 不公开 GetResult()(仅公开结果 属性) - 问题是:
- 是否需要"pull"从ValueTask中取出Result("release"这个ValueTask下可能运行的IValueTaskSource)?
- 如果是:
- 是上面缺少的
.GetAwaiter()
调用吗? - 或者对 属性 的虚假调用是否保证有效
var fake = valueTask.Result;
?总是? (怕死码淘汰。)
- 是上面缺少的
- 如果不是,像下面这样的直接实现是否足够(并且是最佳的)?
public static ValueTask AsNonGenericValueTask<T>( in this ValueTask<T> valueTask )
{
return valueTask.IsCompletedSuccessfully ? default : new ValueTask( valueTask.AsTask() );
}
该代码中缺少的是 .GetAwaiter()
:
public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
if (valueTask.IsCompletedSuccessfully)
{
valueTask.GetAwaiter().GetResult();
return default;
}
return new ValueTask(valueTask.AsTask());
}
你说对了一部分,因为你不关心结果。但是您可能会关心抛出的异常或取消,如果您不查询结果,您会错过这些。
或者你可以这样写:
public static async ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
=> await valueTask;
您可以使用:
valueTask.GetAwaiter().GetResult();
...或者:
_ = valueTask.Result;
两者都委托给基础 IValueTaskSource<T>.GetResult
method, assuming that the ValueTask<T>
is backed by a IValueTaskSource<T>
。使用更短的(第二种)方法应该稍微更有效,因为它涉及更少的方法调用。
您也可以完全忽略获取结果。没有要求必须至少等待一次 ValueTask<T>
,或者必须至少检索一次其结果。收到 ValueTask<T>
然后忘记它是完全有效的。 documented restriction 是:
A
ValueTask<TResult>
instance may only be awaited once, [...]
这是“只能”,而不是“必须”。
虽然得到结果仍然是个好主意。通过检索结果,您表示 ValueTask<TResult>
已被消耗,因此可以重用基础 IValueTaskSource<T>
。重用 IValueTaskSource<T>
个实例使基于 ValueTask<T>
的实现更加高效,因为它们分配内存的频率较低。要获得一个想法,请查看内部 System.Threading.Channels.AsyncOperation<TResult>
class. This class implements the IValueTaskSource<TResult>
interface. Here is how the GetResult
的实现:
/// <summary>Gets the result of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
public TResult GetResult(short token)
{
if (_currentId != token)
{
ThrowIncorrectCurrentIdException();
}
if (!IsCompleted)
{
ThrowIncompleteOperationException();
}
ExceptionDispatchInfo? error = _error;
TResult? result = _result;
_currentId++;
if (_pooled)
{
Volatile.Write(ref _continuation, s_availableSentinel); // only after fetching all needed data
}
error?.Throw();
return result!;
}
将私有字段 Action<object> _continuation
设置为静态只读值 s_availableSentinel
允许 AsyncOperation<TResult>
被后续异步操作重用(例如 ChannelReader.ReadAsync
) .否则下一个异步操作将分配一个新的 AsyncOperation<TResult>
实例。