首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Task.WhenAll for ValueTask

Task.WhenAll for ValueTask
EN

Stack Overflow用户
提问于 2017-08-15 08:36:18
回答 5查看 10.4K关注 0票数 37

是否有相当于Task.WhenAll接受ValueTask

我可以用

代码语言:javascript
复制
Task.WhenAll(tasks.Select(t => t.AsTask()))

如果它们都包装了一个Task,这会很好,但它将强制为真正的ValueTask分配无用的Task对象。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2017-08-15 09:13:12

从设计上看,不。来自医生们

方法可能会返回此值类型的实例,如果它们的操作的结果可能是同步可用的,并且该方法将被如此频繁地调用,那么为每个调用分配一个新任务的成本将是非常高的。 … 例如,考虑一个方法,它可以返回带缓存任务的Task<TResult>作为公共结果,也可以返回ValueTask<TResult>。如果结果的使用者希望将其作为Task<TResult>使用,例如在Task.WhenAllTask.WhenAny等方法中使用with,则首先需要使用AsTaskValueTask<TResult>转换为Task<TResult>,这将导致如果首先使用缓存Task<TResult>,则会避免分配。

票数 23
EN

Stack Overflow用户

发布于 2020-07-28 19:33:36

除非我遗漏了什么,否则我们应该能够等待循环中的所有任务:

代码语言:javascript
复制
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在注释中询问了这个问题)。

例外

当任务抛出异常时,上述代码将停止等待其余任务,只需抛出即可。如果这是不可取的,我们可以:

代码语言:javascript
复制
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>>的重载。

样本签名:

代码语言:javascript
复制
// 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>>);
}

注释中提到了将结果数组/列表作为参数传递的方法,因此我们的实现将不需要所有额外的分配,但是调用方仍然必须创建它。如果它们对等待任务进行批处理,这可能是有意义的,对于我来说,这听起来是一个相当专门的场景,但为了完整起见:

代码语言:javascript
复制
// 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);
}
票数 17
EN

Stack Overflow用户

发布于 2017-08-15 13:07:25

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

代码语言:javascript
复制
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那样保持顺序,如果需要,可以很容易地更改它以实现它。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45689327

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档