问题 Task.WhenAll for ValueTask


有相当于 Task.WhenAll 验收 ValueTask

我可以使用它来解决它

Task.WhenAll(tasks.Select(t => t.AsTask()))

如果它们都包装好,那就没关系了 Task 但它会迫使无用的分配 Task 真实的对象 ValueTask


7363
2017-08-15 08:36


起源

Task.WhenAll(tasks.Where(t => !t.IsCompletedSuccessfully).Select(t => t.AsTask())) - PetSerAl


答案:


按设计,没有。从 文档

方法可能会返回此值类型的实例,因为它们的操作结果可能同步可用,并且预计会频繁调用该方法,以致为每个调用分配新任务的成本将过高。

...

例如,考虑一个可以返回a的方法 Task<TResult> 将缓存任务作为常见结果或a ValueTask<TResult>。如果结果的消费者想要将其用作 Task<TResult>,比如在方法中使用 Task.WhenAll 和 Task.WhenAnyValueTask<TResult> 首先需要转换为 Task<TResult> 运用 AsTask,这导致了一个在缓存时可以避免的分配 Task<TResult> 最初是用过的。

因此,任何异步方法的默认选择应该是返回a Task 要么 Task<TResult>。只有当性能分析证明它值得时才应该 ValueTask<TResult> 用来代替 Task<TResult>


9
2017-08-15 09:13





正如@stuartd指出的那样,设计不支持它,我必须手动实现:

public static async Task<IReadOnlyCollection<T>> WhenAll<T>(this IEnumerable<ValueTask<T>> tasks)
{
    var results = new List<T>();
    var toAwait = new List<Task<T>>();

    foreach (var valueTask in tasks)
    {
        if (valueTask.IsCompletedSuccessfully)
            results.Add(valueTask.Result);
        else
            toAwait.Add(valueTask.AsTask());
    }

    results.AddRange(await Task.WhenAll(toAwait).ConfigureAwait(false));

    return results;
}

当然,这仅对高吞吐量和高数量有帮助 ValueTask 因为它增加了一些其他开销。

注意:正如@StephenCleary指出的那样,这不会保持顺序为 Task.WhenAll 如果需要,可以很容易地改变它来实现它。


2
2017-08-15 13:07



结果集合也可能与原始值任务的顺序不同。 - Stephen Cleary
@StephenCleary考虑到了这一点,但我并不认为它是一个精确的替代品 Task.WhenAll,它可以很容易地修复。 - Stefano d'Antonio