Ste*_*nio 14 c# task-parallel-library
是否有相同的Task.WhenAll接受ValueTask?
我可以使用它来解决它
Task.WhenAll(tasks.Select(t => t.AsTask()))
Run Code Online (Sandbox Code Playgroud)
如果它们都包装了Task它会很好,但它会迫使Task对象无用地分配ValueTask.
stu*_*rtd 11
按设计,没有.来自文档:
方法可能会返回此值类型的实例,因为它们的操作结果可能同步可用,并且预计会频繁调用该方法,以致为每个调用分配新任务的成本将过高.
...
例如,考虑一种方法,该方法可以返回
Task<TResult>带有缓存任务的a作为常见结果或者ValueTask<TResult>.如果结果的消费者想用它作为Task<TResult>,比如在类似的方法使用Task.WhenAll和Task.WhenAny,则ValueTask<TResult>首先需要转换成Task<TResult>使用AsTask,这导致如果高速缓存会被避免了分配Task<TResult>已经使用首先.因此,任何异步方法的默认选择应该是返回一个
Task或Task<TResult>.只有在性能分析证明它值得的时候才应该ValueTask<TResult>使用而不是Task<TResult>.
正如@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;
}
Run Code Online (Sandbox Code Playgroud)
当然,这只会在高吞吐量和高数量的情况下有所帮助,ValueTask因为它会增加一些其他开销。
注意:正如@StephenCleary 指出的那样,这不会保持顺序Task.WhenAll,如果需要,可以轻松更改以实现它。
除非我遗漏了什么,否则我们应该能够在循环中等待所有任务:
public static async ValueTask<T[]> WhenAll<T>(params ValueTask<T>[] tasks)
{
// Argument validations omitted
var results = new T[tasks.Length];
for (var i = 0; i < tasks.Length; i++)
results[i] = await tasks[i].ConfigureAwait(false);
return results;
}
Run Code Online (Sandbox Code Playgroud)
等待 aValueTask同步完成的分配不应导致 aTask被分配。所以这里发生的唯一“额外”分配是我们用于返回结果的数组。
顺序
返回项目的顺序与产生它们的给定任务的顺序相同。
异常
当一个任务抛出异常时,上面的代码将停止等待其余的异常并直接抛出。如果这是不可取的,我们可以这样做:
public static async ValueTask<T[]> WhenAll<T>(params ValueTask<T>[] tasks)
{
Exception? exception = null;
var results = new T[tasks.Length];
for (var i = 0; i < tasks.Length; i++)
try
{
results[i] = await tasks[i].ConfigureAwait(false);
}
catch (Exception ex)
{
// Remember the first exception, swallow the rest
exception ??= ex;
}
return exception is null
? results
: throw exception;
}
Run Code Online (Sandbox Code Playgroud)
我们直接抛出第一个异常,因为用 an 包裹它AggregateException不是ValueTask一回事。
...如果在任务运行过程中发生异常,或者任务已被取消,则 Result 属性不会返回值。相反,尝试访问属性值会引发 AggregateException 异常。
如果此 ValueTask 出现故障,则此属性会引发异常。抛出的异常未包含在 AggregateException 中。
但是如果我们确实希望我们的WhenAll方法抛出一个AggregateException包含所有抛出的异常,我们可以这样做:
public static async ValueTask<T[]> WhenAll<T>(params ValueTask<T>[] tasks)
{
// We don't allocate the list if no task throws
List<Exception>? exceptions = null;
var results = new T[tasks.Length];
for (var i = 0; i < tasks.Length; i++)
try
{
results[i] = await tasks[i].ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions ??= new List<Exception>(tasks.Length);
exceptions.Add(ex);
}
return exceptions is null
? results
: throw new AggregateException(exceptions);
}
Run Code Online (Sandbox Code Playgroud)