简体   繁体   English

ValueTask 的 Task.WhenAll

[英]Task.WhenAll for ValueTask

Is there an equivalent of Task.WhenAll accepting ValueTask ?是否有等效的Task.WhenAll接受ValueTask

I can work around it using我可以解决它使用

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

This will be fine if they're all wrapping a Task but it will force the useless allocation of a Task object for real ValueTask .如果它们都包装了一个Task这会很好,但它会强制为真正的ValueTask分配一个无用的Task对象。

By design, no.按照设计,没有。 From the docs :文档

Methods may return an instance of this value type when it's likely that the result of their operations will be available synchronously and when the method is expected to be invoked so frequently that the cost of allocating a new Task for each call will be prohibitive.当方法的操作结果可能同步可用并且预期方法被频繁调用以致于为每次调用分配新任务的成本过高时,方法可能会返回此值类型的实例。

For example, consider a method that could return either a Task<TResult> with a cached task as a common result or a ValueTask<TResult> .例如,考虑一个方法,它可以返回带有缓存任务的Task<TResult>作为公共结果或ValueTask<TResult> If the consumer of the result wants to use it as a Task<TResult> , such as to use with in methods like Task.WhenAll and Task.WhenAny , the ValueTask<TResult> would first need to be converted into a Task<TResult> using AsTask , which leads to an allocation that would have been avoided if a cached Task<TResult> had been used in the first place.如果结果的使用者想要将其用作Task<TResult> ,例如在Task.WhenAllTask.WhenAny等方法中使用,则首先需要将ValueTask<TResult>转换为Task<TResult>使用AsTask ,这会导致分配,如果首先使用缓存的Task<TResult>就可以避免这种分配。

As @stuartd pointed out, it is not supported by design, I had to implement this manually:正如@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;
}

Of course this will help only in high throughput and high number of ValueTask as it adds some other overheads.当然,这只会在高吞吐量和大量ValueTask有所帮助,因为它会增加一些其他开销。

NOTE: As @StephenCleary pointed out, this does not keep the order as Task.WhenAll does, if it is required it can be easily changed to implement it.注意:正如@StephenCleary 指出的那样,这不会像Task.WhenAll那样保持顺序,如果需要,可以轻松更改以实现它。

Unless there is something I'm missing, we should be able to just await all the tasks in a loop:除非我遗漏了什么,否则我们应该能够在循环中等待所有任务:

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;
}

Allocations分配
Awaiting a ValueTask that is completed synchronously shouldn't cause a Task to be allocated.等待同步完成的ValueTask不应导致Task被分配。 So the only "extra" allocation happening here is of the array we use for returning the results.所以这里发生的唯一“额外”分配是我们用于返回结果的数组。

Order订购
Order of the returned items are the same as the order of the given tasks that produce them.返回项目的顺序与产生它们的给定任务的顺序相同。

Exceptions例外
When a task throws an exception, the above code would stop waiting for the rest of the exceptions and just throw.当一个任务抛出异常时,上面的代码将停止等待其余的异常并直接抛出。 If this is undesirable, we could do:如果这是不可取的,我们可以这样做:

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;
}

We throw the first exception directly as wrapping it with an AggregateException is not a ValueTask thing.我们直接抛出第一个异常,因为用AggregateException包装它不是ValueTask事情。

Task<T>.Result remarks : Task<T>.Result备注

...if an exception occurred during the operation of the task, or if the task has been cancelled, the Result property does not return a value. ...如果在任务运行过程中发生异常,或者任务已被取消,则 Result 属性不会返回值。 Instead, attempting to access the property value throws an AggregateException exception .相反,尝试访问属性值会引发 AggregateException 异常

ValueTask<T>.Result remarks : ValueTask<T>.Result备注

If this ValueTask has faulted, this property raises an exception.如果此 ValueTask 出现故障,则此属性会引发异常。 The thrown exception is not wrapped in an AggregateException .抛出的异常未包含在 AggregateException 中

But if we did want our WhenAll method to throw an AggregateException containing all the exceptions thrown, we could do:但是如果我们确实希望我们的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);
}

Here is a variant of the WhenAll extension method that attempts to improve upon Stefano d'Antonio's answer , by restoring the order of the results according to the original order of the ValueTask 's. 这是WhenAll扩展方法的一种变体,它尝试通过根据ValueTask的原始顺序恢复结果的顺序来改进Stefano d'Antonio的答案


Update: I discarded the previous implementation for a new one that removes the need of sorting the results. 更新:我放弃了以前的实现,而不再使用对结果进行排序的新实现。

public static async Task<TResult[]> WhenAll<TResult>(
    this IEnumerable<ValueTask<TResult>> tasks)
{
    // The volatile property IsCompleted must be accessed only once
    var tasksArray = tasks.Select(t => t.IsCompleted ?
        (
            HasResult : true,
            Result: t.Result,
            Task: (Task<TResult>)null
        ) : (
            HasResult : false,
            Result: default(TResult),
            Task: t.AsTask()
        ))
        .ToArray();

    var pendingTasks = tasksArray
        .Where(t => !t.HasResult)
        .Select(t => t.Task);

    await Task.WhenAll(pendingTasks).ConfigureAwait(false);

    return tasksArray
        .Select(t => t.HasResult ? t.Result : t.Task.Result)
        .ToArray();
}
    public static class TaskExtensions
    {
        /// <summary>
        /// Wait for all ValueTasks to complete in a IEnumerable<ValueTask>
        /// </summary>
        public static async Task WhenAllValueTasks(this IEnumerable<ValueTask> tasks)
        {
            foreach (var taskToAWait in tasks)
            {
                await taskToAWait;
            }
        }
    }

Tried to do some optimization, result returned in correct order and correct exception handling.尝试做一些优化,结果以正确的顺序返回并正确处理异常。

public static ValueTask<T[]> WhenAll<T>(IEnumerable<ValueTask<T>> tasks)
    {
        var list = tasks.ToList();
        var length = list.Count;
        var result = new T[length];
        var i = 0;

        for (; i < length; i ++)
        {
            if (list[i].IsCompletedSuccessfully)
            {
                result[i] = list[i].Result;
            }
            else
            {
                return WhenAllAsync();
            }
        }

        return new ValueTask<T[]>(result);

        async ValueTask<T[]> WhenAllAsync()
        {
            for (; i < length; i ++)
            {
                try
                {
                    result[i] = await list[i];
                }
                catch
                {
                    for (i ++; i < length; i ++)
                    {
                        try
                        {
                            await list[i];
                        }
                        catch
                        {
                            // ignored
                        }
                    }

                    throw;
                }
            }

            return result;
        }
    }

I'm using this extension method:我正在使用这种扩展方法:

internal static class ValueTaskExtensions
{
    public static Task WhenAll(this IEnumerable<ValueTask> tasks)
    {
        return Task.WhenAll(tasks.Select(v => v.AsTask()));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM