Task.WhenAll for ValueTask

时间:2017-08-15 08:36:19

标签: c# task-parallel-library

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

我可以使用

解决这个问题
Task.WhenAll(tasks.Select(t => t.AsTask()))

如果它们全部包裹Task,这将会很好,但它会强制将Task对象无用地分配给真正的ValueTask

6 个答案:

答案 0 :(得分:9)

按设计,没有。来自the docs

  

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

     

...

     

例如,考虑一种方法,该方法可以返回带有缓存任务的Task<TResult>作为常见结果或ValueTask<TResult>。如果结果的使用者希望将其用作Task<TResult>,例如在Task.WhenAllTask.WhenAny等方法中使用,则首先需要转换ValueTask<TResult>使用Task<TResult>进入AsTask,如果首先使用了缓存的Task<TResult>,则可以避免分配。

     

因此,任何异步方法的默认选择应该是返回TaskTask<TResult>。只有在性能分析证明有价值时,才应使用ValueTask<TResult>代替Task<TResult>

答案 1 :(得分:2)

正如@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 :(得分:1)

除非我丢失了某些东西,否则我们应该能够在循环中等待所有任务:

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

分配
等待同步完成的ValueTask不会导致分配Task。因此,这里发生的唯一“额外”分配是用于返回结果的数组。

订购
返回项目的顺序与产生它们的给定任务的顺序相同。

例外
当任务引发异常时,以上代码将停止等待其余异常,而仅引发。如果不希望这样做,我们可以这样做:

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

我们直接抛出第一个异常是因为用AggregateException包装它不是ValueTask

Task<T>.Result remarks

...如果在任务操作期间发生异常,或者任务已被取消,则Result属性不会返回值。而是尝试访问属性值引发AggregateException异常

ValueTask<T>.Result remarks

如果此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);
}

答案 3 :(得分:0)

我正在使用这种扩展方法:

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

答案 4 :(得分:0)

这里是WhenAll扩展方法的一种变体,它试图通过根据ValueTask的原始顺序恢复结果的顺序来改进Stefano d'Antonio的answer。的。


更新:我放弃了以前的实现,而不再使用对结果进行排序的新实现。

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

答案 5 :(得分:-2)

试图进行一些优化,以正确的顺序返回结果并进行正确的异常处理。

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