【问题标题】:Awaiting a sequence of tasks to run sequentially等待一系列任务按顺序运行
【发布时间】:2020-02-04 21:50:22
【问题描述】:

我想创建一个通用方法来等待一系列任务按顺序完成,检索每个任务的结果。这是我创建的代码:

public static class TaskMixin 
{
    public static async Task<IEnumerable<T>> AwaitAll<T>(this IEnumerable<Task<T>> tasks)
    {
        var results = new List<T>();
        foreach (var t in tasks)
        {
            results.Add(await t);
        }

        return results;
    }
}

有更好的或内置的方法吗?

通知

在编写上述方法之前,我尝试过使用内置的Task.WhenAll 方法,但这给我带来了麻烦,因为这些任务使用的是实体框架的DbContext,而且它们似乎是同时运行的。

这是我使用Task.WhenAll时遇到的异常。

第二个操作在前一个上下文之前开始 异步操作完成

【问题讨论】:

    标签: c# .net concurrency async-await


    【解决方案1】:

    无法确保传递给此方法的任务尚未运行,实际上它们可能已经启动了热任务。如果任务已经在运行,那么您只需按顺序等待它们。如果您希望操作按顺序运行,那么您需要调用按顺序返回任务的方法。

    或者对于 EF 的 DbContext,通常最好为每个请求使用一个新的 DbContext。但是,如果您执行许多并行查询,那么您可能会以错误的方式解决问题。许多并行查询并不一定意味着您的查询会运行得更快。

    但是,您可以通过像这样获取Func&lt;Task&lt;T&gt;&gt; 委托来按顺序抽象出正在运行的操作:

    public async Task<IEnumerable<T>> RunSequentiallyAsync<T>(Func<Task<T>>[] operations)
    {
        var results = new List<T>();
        foreach (var op in operations)
        {
            results.Add(await op());
        }
        return results;
    }
    

    【讨论】:

    • IEnumerable&lt;Task&lt;T&gt;&gt; 可能是一个真正的延迟枚举,在这种情况下,任务将在枚举期间一个接一个地创建。但是你是对的,没有办法确保它。另一方面,该方法被命名为AwaitAll,这与它的承诺是一致的。
    【解决方案2】:

    按顺序等待任务的另一种方法是使用 IAsyncEnumerable 和 await foreach 语法。

    这里是一个示例扩展方法和一个使用它的控制台应用程序。控制台应用程序反转任务以证明这些任务确实是按顺序调用的。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace SO60066033
    {
        public class Program
        {
            static async Task Main(string[] args)
            {
                var someTasks = new Func<Task<int>>[]
                {
                    () => Task.FromResult(1),
                    () => Task.FromResult(2),
                    () => Task.FromResult(3),
                    () => Task.FromResult(4),
                };
                var results = someTasks.AwaitAll();
                await foreach (var result in results)
                    Console.WriteLine(result);
                var reversedTasks = someTasks.Reverse().ToList();
                var reversedResults = reversedTasks.AwaitAll();
                await foreach (var result in reversedResults)
                    Console.WriteLine(result);
            }
        }
        public static class TaskExtensions
        {
            public static async IAsyncEnumerable<T> AwaitAll<T>(this IEnumerable<Func<Task<T>>> tasks)
            {
                foreach (var task in tasks)
                    yield return await task();
            }
        }
    }
    

    【讨论】:

    • 我会将变量 task/tasks 重命名为 taskFactory/taskFactories。您也没有提到将返回类型从 Task&lt;IEnumerable&lt;T&gt;&gt; 更改为 IAsyncEnumerable&lt;T&gt; 是否有任何优点或缺点。
    猜你喜欢
    • 1970-01-01
    • 2017-04-19
    • 1970-01-01
    • 2022-06-19
    • 2015-02-15
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多