是否有相当于Task.WhenAll接受ValueTask的
我可以用
Task.WhenAll(tasks.Select(t => t.AsTask()))如果它们都包装了一个Task,这会很好,但它将强制为真正的ValueTask分配无用的Task对象。
发布于 2017-08-15 09:13:12
从设计上看,不。来自医生们
方法可能会返回此值类型的实例,如果它们的操作的结果可能是同步可用的,并且该方法将被如此频繁地调用,那么为每个调用分配一个新任务的成本将是非常高的。 … 例如,考虑一个方法,它可以返回带缓存任务的
Task<TResult>作为公共结果,也可以返回ValueTask<TResult>。如果结果的使用者希望将其作为Task<TResult>使用,例如在Task.WhenAll和Task.WhenAny等方法中使用with,则首先需要使用AsTask将ValueTask<TResult>转换为Task<TResult>,这将导致如果首先使用缓存Task<TResult>,则会避免分配。
发布于 2020-07-28 19:33:36
除非我遗漏了什么,否则我们应该能够等待循环中的所有任务:
public static async ValueTask<T[]> WhenAll<T>(params ValueTask<T>[] tasks)
{
ArgumentNullException.ThrowIfNull(tasks);
if (tasks.Length == 0)
return Array.Empty<T>();
var results = new T[tasks.Length];
for (var i = 0; i < tasks.Length; i++)
results[i] = await tasks[i].ConfigureAwait(false);
return results;
}拨款
等待同步完成的ValueTask不应该导致分配Task。因此,这里唯一的“额外”分配是用于返回结果的数组。
阶
返回项的顺序与生成它们的给定任务的顺序相同。
并发
尽管我们看起来是按顺序执行任务,但实际上并非如此,因为调用此方法时,任务已经启动(即处于热状态)。因此,我们只等待数组中最长的任务(谢谢Sergey在注释中询问了这个问题)。
例外
当任务抛出异常时,上述代码将停止等待其余任务,只需抛出即可。如果这是不可取的,我们可以:
public static async ValueTask<T[]> WhenAll<T>(params ValueTask<T>[] tasks)
{
ArgumentNullException.ThrowIfNull(tasks);
if (tasks.Length == 0)
return Array.Empty<T>();
// 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(tasks.Length);
exceptions.Add(ex);
}
return exceptions is null
? results
: throw new AggregateException(exceptions);
}额外注意事项
IEnumerable<ValueTask<T>>和IReadOnlyList<ValueTask<T>>的重载。样本签名:
// There are some collections (e.g. hash-sets, queues/stacks,
// linked lists, etc) that only implement I*Collection interfaces
// and not I*List ones, but A) we're not likely to have our tasks
// in them and B) even if we do, IEnumerable accepting overload
// below should handle them. Allocation-wise; it's a ToList there
// vs GetEnumerator here.
public static async ValueTask<T[]> WhenAll<T>(
IReadOnlyList<ValueTask<T>> tasks)
{
// Our implementation above.
}
// ToList call below ensures that all tasks are initialized, so
// calling this with an iterator wouldn't cause the tasks to run
// sequentially.
public static ValueTask<T[]> WhenAll<T>(
IEnumerable<ValueTask<T>> tasks)
{
return WhenAll(tasks?.ToList());
}
// Arrays already implement IReadOnlyList<T>, but this overload
// is still useful because the `params` keyword allows callers
// to pass individual tasks like they are different arguments.
public static ValueTask<T[]> WhenAll<T>(
params ValueTask<T>[] tasks)
{
return WhenAll(tasks as IReadOnlyList<ValueTask<T>>);
}注释中提到了将结果数组/列表作为参数传递的方法,因此我们的实现将不需要所有额外的分配,但是调用方仍然必须创建它。如果它们对等待任务进行批处理,这可能是有意义的,对于我来说,这听起来是一个相当专门的场景,但为了完整起见:
// Arrays implement `IList<T>`
public static async ValueTask WhenAll<T>(ValueTask<T>[] source, IList<T> target)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(target);
if (source.Length != target.Count)
throw new ArgumentException(
"Source and target lengths must match",
nameof(target));
List<Exception>? exceptions = null;
for (var i = 0; i < source.Length; i++)
try
{
target[i] = await source[i].ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions ??= new(source.Length);
exceptions.Add(ex);
}
if (exceptions is not null)
throw new AggregateException(exceptions);
}发布于 2017-08-15 13:07:25
正如@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那样保持顺序,如果需要,可以很容易地更改它以实现它。
https://stackoverflow.com/questions/45689327
复制相似问题