C# 异步/等待读取 DbDataReader 的效率(或滥用)

标签 c# .net asynchronous .net-core-2.1 compiler-generated

偶然发现了一段相对常用的代码,起初看起来效率很低。 (我知道有时优化可能是邪恶的,但我想知道)

介绍部分 - 相当简单的 SP 执行 + 读取返回的数据:

try
{
    await connection.OpenAsync();
    using (var command = connection.CreateCommand())
    {
        command.CommandText = sql.ToString();
        command.Parameters.AddRange(sqlParameters.ToArray());

        var reader = await command.ExecuteReaderAsync();
        if (reader.HasRows)
        {
            while (await reader.ReadAsync())
            {
                 var item = await GetProjectElement(reader);
                 list.Add(item);
            }
         }

         reader.Dispose();
     }      
}
finally
{
    connection.Close();
}

我担心的是功能

await GetProjectElement(reader)

private async Task<Project> GetProjectElement(DbDataReader reader)
{
    var item = new Project
    {
        Id = await reader.GetFieldValueAsync<int>(1),
        ParentId = await reader.IsDBNullAsync(2) ? default(int?) : await reader.GetFieldValueAsync<int>(2),
        Name = await reader.IsDBNullAsync(3) ? default(string) : await reader.GetFieldValueAsync<string>(3),
        Description = await reader.IsDBNullAsync(4) ? default(string) : await reader.GetFieldValueAsync<string>(4),
        Address = await reader.IsDBNullAsync(5) ? default(string) : await reader.GetFieldValueAsync<string>(5),
        City = await reader.IsDBNullAsync(6) ? default(string) : await reader.GetFieldValueAsync<string>(6),
        PostalCode = await reader.IsDBNullAsync(7) ? default(string) : await reader.GetFieldValueAsync<string>(7),
        Type = (ProjectTypeEnum)(await reader.GetFieldValueAsync<byte>(8)),
        StartDate = await reader.IsDBNullAsync(9) ? default(DateTime?) : await reader.GetFieldValueAsync<DateTime>(9),
        EstimatedEndDate = await reader.IsDBNullAsync(10) ? default(DateTime?) : await reader.GetFieldValueAsync<DateTime>(10),
        ActualEndDate = await reader.IsDBNullAsync(11) ? default(DateTime?) : await reader.GetFieldValueAsync<DateTime>(11),
        WebsiteUrl = await reader.IsDBNullAsync(12) ? default(string) : await reader.GetFieldValueAsync<string>(12),
        Email = await reader.IsDBNullAsync(13) ? default(string) : await reader.GetFieldValueAsync<string>(13),
        PhoneNumber = await reader.IsDBNullAsync(14) ? default(string) : await reader.GetFieldValueAsync<string>(14),
        MobilePhoneNumber = await reader.IsDBNullAsync(15) ? default(string) : await reader.GetFieldValueAsync<string>(15),
        Key = await reader.IsDBNullAsync(16) ? default(Guid?) : await reader.GetFieldValueAsync<Guid>(16),
        OrganizationElementId = await reader.GetFieldValueAsync<int>(17),
        CompanyOrganizationElementId = await reader.IsDBNullAsync(18) ? default(int?) : await reader.GetFieldValueAsync<int>(18),
        IsArchived = await reader.GetFieldValueAsync<bool>(20),
        IsDeleted = await reader.GetFieldValueAsync<bool>(21),
        CreatedOn = await reader.GetFieldValueAsync<DateTime>(22),
        CreatedBy = await reader.GetFieldValueAsync<string>(23),
        ModifiedOn = await reader.IsDBNullAsync(24) ? default(DateTime?) : await reader.GetFieldValueAsync<DateTime>(24),
        ModifiedBy = await reader.IsDBNullAsync(25) ? default(string) : await reader.GetFieldValueAsync<string>(25)
    };

    return item;
}

如您所见,有很多等待调用被编译器转换为状态机,不是吗?

您可以找到编译器生成代码的简化版本 here . 大量 GOTO,这意味着上下文会一遍又一遍地切换。

由于 SP 是在没有指定 CommandBehavior 的情况下执行的 - 数据将处于非顺序模式。 (可能的原因是对于 Project link 的这种情况,表格行的字节数不应该很大)


我的问题是:

1) 这是在没有明显原因的情况下滥用 async/await,因为行数据已经缓冲在内存中,对吗?

2) 是 Task<Project>在这种情况下是纯开销吗?

3) 与没有 await 的方法相比,这种方法的性能实际上会更差吗?正在


最后的想法:如果我做对了,我们会想对内容可能超过合理长度的大表行使用 CommandBehavior.SequentialAccess,从而使我们想要异步读取它? (比如存储 varbinary(max) 或 blob)

最佳答案

正如其他人所指出的,GOTO 不会导致上下文切换,而且速度非常快。

1) is this abusing of the async/await without an obvious reason, because the row data is already buffered in memory, right?

ADO.NET 允许实现者在实现基类型的确切方式上有很大的余地。也许该行在内存中,也许不在。

2) is Task a pure overhead in this scenario?

是的,如果操作实际上是同步的。这是您的 ADO.NET 提供程序的实现细节。

注意状态机和await这里几乎不增加任何开销;有一个async fast path如果可能,代码只是保持同步执行。

3) would this approach actually have a worse performance compared to one without awaiting

可能不会。首先,性能影响不会受到调用每个方法并继续同步执行所完成的少量 CPU 工作的驱动。您看到的任何性能影响都是由于额外的 Task<T>实例扔到 Gen0 堆上,必须进行垃圾回收。这就是现在有 ValueTask<T> 的原因.

但即使是这种性能影响也很可能在对数据库服务器的网络 I/O 调用旁边不明显。也就是说,如果您想了解微观性能损失,Zen of Async是经典。

关于C# 异步/等待读取 DbDataReader 的效率(或滥用),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54217004/

相关文章:

java - Android Retrofit Post 请求的服务器超时

.NET 异步套接字 : any benefit of SocketAsyncEventArgs over Begin/End in this scenario?

c# - 如何在 ValueInjecter 3.1 中指定自定义匹配规则?

c# - Socket.SendAsync 需要几秒钟才能完成

c# - 无法访问 Newtonsoft.Json.Linq.JProperty 上的子值 - 使用 LinQ 检查 JObject 时发生错误

c# - 在 .NET 中导入模块然后将其用作对象?

objective-c - 在 Objective-C 中同步异步任务

c# - 如何从代码而不是配置中将值设置为 RedisSetting?

.net - 代理对检测失败

.net - 查找垃圾收集器上次在 .NET 中运行的时间