【问题标题】:Async/Await Multiple CountAsync Database Calls EFCore异步/等待多个 CountAsync 数据库调用 EFCore
【发布时间】:2019-07-27 01:21:31
【问题描述】:

我正在努力确保我理解 async/await。在以下示例中,我的代码是异步运行还是同步运行?

我的理解可能是错误的,即每个异步数据库调用都不必等待上一个调用完成。因此,我基本上可以运行大量 CountAsync 调用,它们会同时运行,直到此时某些东西试图从其中一个异步调用中获取数据。

这是我目前拥有的:(所有选择/位置逻辑已被删除,因为这个问题不需要它)

public async Task<DashboardModel> GetDashboard(DashboardInput input)
    {
        DashboardModel model = new DashboardModel();
        model.MyCustomers = await _context.Customers.Where(x => [...]).Select(x => new DashboardCustomerModel()
        {
            [...]
        }).ToListAsync();

        model.TotalCustomers = await _context.Customers.CountAsync(x => [...]);

        model.MyTotalCustomers = await _context.Customers.CountAsync(x => [...]);
        model.MyClosedCustomers = await _context.Customers.CountAsync(x => [...]);
        model.MyNotStartedCustomers = await _context.Customers.CountAsync(x => [...]);

        model.OtherTotalCustomers = await _context.Customers.CountAsync(x => [...]);
        model.OtherClosedCustomers = await _context.Customers.CountAsync(x => [...]);
        model.OtherNotStartedCustomers = await _context.Customers.CountAsync(x => [...]);

        model.PreparerApprovedCustomers = await _context.Customers.CountAsync(x => [...]);
        model.ReviewerApprovedCustomers = await _context.Customers.CountAsync(x => [...]);
        model.ApprovedCustomers = await _context.Customers.CountAsync(x => [...]);

        return model;
    }

我的同事说这是不正确的,所有的调用都会同步运行;因此我问这个问题的原因。如果我错了,那么编写此方法以使所有异步调用同时运行的正确方法是什么?

【问题讨论】:

  • 您似乎混淆了“asynchronously”和“并行”。您的查询将一个接一个地异步顺序运行。这就是await 所做的。如果您希望异步任务并行运行,请参阅stackoverflow.com/q/19431494/11683。但是你cannot do that with EF
  • 如果您希望调用并行运行,则将生成的任务收集到一个集合中,然后使用Task.WhenAll 等待它们。
  • @GSerg:您实际上可以使用 EF 做到这一点,但在所有情况下都这样做并不安全。例如,不支持尝试多次异步更新,因此查询只会在命中时命中数据库,并且在某些情况下可能会导致奇怪或不正确的行为。然而,像运行计数这样的事情是 100% 完全可以异步完成的,因为它对数据没有影响。

标签: c# asp.net-core async-await entity-framework-core entity-framework-core-2.2


【解决方案1】:

任务返回热,或已经开始。 await 关键字的字面意思是在任务完成之前停止。所以,是的,使用您拥有的代码,每个查询将按顺序依次运行,一次一个,在您继续进行时,然后在每一行停止。

要并行运行,您只需启动任务即可。例如:

var totalCustomersTask = _context.Customers.CountAsync(x => [...]);

var myTotalCustomersTask = _context.Customers.CountAsync(x => [...]);
var myClosedCustomers = _context.Customers.CountAsync(x => [...]);
var myNotStartedCustomers = _context.Customers.CountAsync(x => [...]);

...

请注意,这些行中的任何一行都没有await。然后,在你开始所有任务之后:

model.TotalCustomers = await totalCustomersTask;

model.MyTotalCustomers = await myTotalCustomersTask;
model.MyClosedCustomers = await myClosedCustomersTask;
model.MyNotStartedCustomers = await myNotStartedCustomers;

...

虽然异步操作可以并行运行,但异步与“并行”不同。

【讨论】:

  • 不,那个won't work with EF。即使没有await,您也必须await 上一个任务才能调用另一个任务。
  • 那我很困惑。该答案中引用的文档似乎声称 EF 将检测开发人员是否尝试一次执行两个异步操作并抛出。 这不是真的吗?如果不是,它如何处理事务完整性?它会促进分布式事务吗?或者查询是否通过相同的连接/事务,仍然按顺序进行,但没有顺序保证?有参考吗?
  • @Ben 查看我之前的评论。虽然它不会抛出异常,但查询不会并行运行。获得并行查询的唯一方法是使用单独的数据库上下文实例。期间。
  • @Ben 在 C# 级别,不一定。在 SQL 级别,可能使用count(case when closed = 1 then 1 else null end) as closed_customers 等创建一个一次性计算所有计数的视图会更快。
  • @Chris 不确定您所说的生产环境是什么意思。我正在使用简单的控制台应用程序。但没关系,您没有显式生成线程 (Task.Run),因此异步任务正在“运行”,而不是 SQL 查询。将它们序列化的是 EF Core DbContext 异步监视器。同样,您可以看到,如果您打开 EF Core 命令执行日志记录并查看命令执行/执行日志。在完全相同的环境中,如果我将查询更改为使用单独的数据库上下文实例,则日志会显示并行命令执行。简而言之,它是 EF Core db 上下文实例级序列化。
猜你喜欢
  • 2013-08-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-04
相关资源
最近更新 更多