c# - 个人等待与 Task.WhenAll

标签 c# sql .net asynchronous async-await

我有以下两种方法,它们产生相同的结果。

public static async Task<IEnumerable<RiskDetails>> ExecuteSqlStoredProcedureSelect<T>(IEnumerable<AccountInfo> linkedAccounts, string connectionString, string storedProcedure, int connTimeout = 10)
{
        var responseList = new List<RiskDetails>();

        using (IDbConnection conn = new SqlConnection(connectionString))
        {
            foreach (var account in linkedAccounts)
            {
                var enumResults = await conn.QueryAsync<RiskDetails>(storedProcedure, 
                    new { UserID = account.UserID, CasinoID = account.CasinoID, GamingServerID = account.GamingServerID, AccountNo = account.AccountNumber, Group = account.GroupCode, EmailAddress = account.USEMAIL }, 
                    commandType: CommandType.StoredProcedure);
                    
                if (enumResults != null)
                        foreach (var response in enumResults)
                            responseList.Add(response);
            }
         }

         return responseList;
    }
        
    public static async Task<IEnumerable<RiskDetails>> ExecuteSqlStoredProcedureSelectParallel<T>(IEnumerable<AccountInfo> linkedAccounts, string connectionString, string storedProcedure, int connTimeout = 10)
    {
        List<Task<IEnumerable<RiskDetails>>> tasks = new List<Task<IEnumerable<RiskDetails>>>();
        var responseList = new List<RiskDetails>();

        using (IDbConnection conn = new SqlConnection(connectionString))
        {
            conn.Open();

            foreach (var account in linkedAccounts)
            {
                var enumResults = conn.QueryAsync<RiskDetails>(storedProcedure,
                        new { UserID = account.UserID, CasinoID = account.CasinoID, GamingServerID = account.GamingServerID, AccountNo = account.AccountNumber, Group = account.GroupCode, EmailAddress = account.USEMAIL },
                        commandType: CommandType.StoredProcedure, commandTimeout: 0);

                //add task
                tasks.Add(enumResults);
            }

            //await and get results
            var results = await Task.WhenAll(tasks);
            foreach (var value in results)
                foreach (var riskDetail in value)
                    responseList.Add(riskDetail);
        }

        return responseList;
    }

我对 ExecuteSqlStoredProcedureSelect 执行方式的理解如下:

  • 为帐户 #1 执行查询
  • 等待查询 #1 的结果
  • 接收查询 #1 的结果
  • 为帐户 #2 执行查询
  • 等待查询 #2 的结果
  • 等等

我对 ExecuteSqlStoredProcedureSelectParallel 执行方式的理解如下:

  • 将所有任务添加到一个 IEnumerable 实例
  • 调用 Task.WhenAll,它将开始执行对帐户 #n 的查询
  • 查询相对于 SQL 服务器并行执行
  • Task.WhenAll 在所有查询执行时返回

根据我的理解,ExecuteSqlStoredProcedureSelectParallel 这个函数在时间方面应该有一点改进,但​​目前还没有。

我的理解错了吗?

最佳答案

您对 ExecuteSqlStoredProcedureSelectParalel 的理解并不完全正确。

Call Task.WhenAll, which will start executing queries for Account #n

Task.WhenAll 不启动任何东西。 QueryAsync 方法返回后 - 任务已经开始并正在运行甚至完成。当控件到达 Task.WhenAll - 所有任务都已经开始。

Queries are executed relatively parallel against SQL server

这是个复杂的话题。为了能够同时在同一个 sql 连接上执行多个查询 - 您在连接字符串中启用了 MultipleActiveResultSets 选项,否则将无法工作(抛出异常)。

然后,在很多地方,包括documentation ,您可以了解到 MARS 不是并行执行。它是关于语句交​​错的,这意味着 SQL Server 可能会在同一连接上执行的不同语句之间切换,就像操作系统可能会在线程之间切换(在单核上)一样。引用上面的链接:

MARS operations execute synchronously on the server. Statement interleaving of SELECT and BULK INSERT statements is allowed. However, data manipulation language (DML) and data definition language (DDL) statements execute atomically. Any statements attempting to execute while an atomic batch is executing are blocked. Parallel execution at the server is not a MARS feature.

现在,即使您的选择查询在服务器上并行执行,如果这些查询执行速度很快,那在“性能”方面对您帮助不大。

假设您查询 10 个帐户,每次查询执行需要 1 毫秒(很正常,我想是预期的情况)。但是,每个查询返回 100 行。现在,这 100 行应该通过网络传递给调用者。这是成本最高的部分,执行时间与之相比可以忽略不计(在此特定示例中)。无论您是否使用 MARS,您与 sql server 之间只有一个物理连接。即使您的 10 个查询是在服务器上并行执行的(由于上述原因,我对此表示怀疑)——它们的结果也无法并行传递给您,因为您只有一个物理连接。因此,在这两种情况下,10*100 = 1000 行都会“按顺序”传送给您。

从这里可以清楚地看出,您不应该期望您的 Parallel 版本执行得明显更快。如果您希望它真正并行 - 为每个命令使用单独的连接。

我还想补充一点,在这种情况下,您机器上的物理内核数量对性能没有不可忽略的影响。异步 IO 与阻塞线程无关,您可能会在 Internet 上的许多地方阅读。

关于c# - 个人等待与 Task.WhenAll,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50560415/

相关文章:

C# cosmos 文件 IO 和目录

c# - C# WebBrowser Control 和我电脑上安装的 IE 有什么关系?

c# - 为什么这个字符串属性在不应该被覆盖的情况下显示为被完全覆盖?

c# - 如何在 Windows Phone 上调用 Setter 中的异步方法

mysql - Mysql中查找重复或更多,删除除第一个输入之外的其他

c# - 我怎么知道 WPF 窗口在哪个监视器中

c# - 从头开始的模型- View - Controller 架构最佳实践

mysql - 不是通过内部连接连接两个查询

SQL计算(Oracle)

c# - 等效于 Dispatcher.Invoke 或 Dispatcher.RunAsync 的可移植类库