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

C# 读取DbDataReader时的异步/等待效率(或滥用),c#,.net,asynchronous,.net-core-2.1,compiler-generated,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

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

简介部分-相当简单的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();
}
让我担心的是功能

等待GetProjectElement(读取器)

专用异步任务GetProjectElement(DbDataReader)
{
var项目=新项目
{
Id=等待读取器。GetFieldValueAsync(1),
ParentId=await reader.IsDBNullAsync(2)?默认值(int?):await reader.GetFieldValueAsync(2),
Name=wait reader.IsDBNullAsync(3)?默认值(字符串):wait reader.GetFieldValueAsync(3),
Description=wait reader.IsDBNullAsync(4)?默认值(字符串):wait reader.GetFieldValueAsync(4),
Address=await reader.IsDBNullAsync(5)?默认值(字符串):await reader.GetFieldValueAsync(5),
City=await reader.IsDBNullAsync(6)?默认值(字符串):await reader.GetFieldValueAsync(6),
PostalCode=await reader.IsDBNullAsync(7)?默认值(字符串):await reader.GetFieldValueAsync(7),
Type=(ProjectTypeEnum)(等待reader.GetFieldValueAsync(8)),
StartDate=await reader.IsDBNullAsync(9)?默认值(日期时间?):await reader.GetFieldValueAsync(9),
EstimatedEndDate=await reader.IsDBNullAsync(10)?默认值(日期时间?):await reader.GetFieldValueAsync(10),
ActualEndDate=await reader.IsDBNullAsync(11)?默认值(DateTime?):await reader.GetFieldValueAsync(11),
WebsiteUrl=await reader.IsDBNullAsync(12)?默认值(字符串):await reader.GetFieldValueAsync(12),
Email=wait reader.IsDBNullAsync(13)?默认值(字符串):wait reader.GetFieldValueAsync(13),
PhoneNumber=await reader.IsDBNullAsync(14)?默认值(字符串):await reader.GetFieldValueAsync(14),
MobilePhoneNumber=await reader.IsDBNullAsync(15)?默认值(字符串):await reader.GetFieldValueAsync(15),
Key=wait reader.IsDBNullAsync(16)?默认值(Guid?):wait reader.GetFieldValueAsync(16),
OrganizationElementId=await reader.GetFieldValueAsync(17),
CompanyOrganizationElementId=await reader.IsDBNullAsync(18)?默认值(int?):await reader.GetFieldValueAsync(18),
IsArchived=wait reader.GetFieldValueAsync(20),
IsDeleted=wait reader.GetFieldValueAsync(21),
CreatedOn=wait reader.GetFieldValueAsync(22),
CreatedBy=wait reader.GetFieldValueAsync(23),
ModifiedOn=wait reader.IsDBNullAsync(24)?默认值(DateTime?):wait reader.GetFieldValueAsync(24),
ModifiedBy=await reader.IsDBNullAsync(25)?默认值(字符串):await reader.GetFieldValueAsync(25)
};
退货项目;
}
正如您所看到的,编译器会将许多等待调用转换为状态机,不是吗

您可以找到编译器生成的代码的简化版本。
大量的GoTo意味着一次又一次的上下文切换

由于执行SP时未指定CommandBehavior,因此数据将处于非顺序模式。(可能是因为在这种情况下,
Project
,表行的字节数不太大)


我的问题是

1) 这是不是因为行数据已经在内存中缓冲,所以滥用async/Wait没有明显的原因,对吗

2) 在这种情况下,
Task
是纯开销吗

3) 与没有等待的方法相比,这种方法的性能会更差吗



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

正如其他人所指出的,GOTO不会引起上下文切换,而且速度相当快

1) 这是不是因为行数据已经在内存中缓冲,所以滥用async/Wait没有明显的原因,对吗

ADO.NET允许实现者在如何准确实现基本类型方面有很大的回旋余地。也许这一行在内存中,也许不是

2) 在这种情况下,任务是纯开销吗

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

请注意,状态机和
wait
在这里几乎不增加任何开销;如果可能的话,代码会保持同步执行

3) 与没有等待的方法相比,这种方法的性能会更差吗

可能不会。首先,性能影响不会由调用每个方法并继续同步执行所做的少量CPU工作来驱动。您所看到的任何性能影响都是由于Gen0堆上抛出了额外的
任务
实例,并且必须进行垃圾收集。这就是为什么现在有一个
ValueTask


但是,即使在对数据库服务器的网络I/O调用旁边,这种性能影响也很可能不明显。这就是说,如果你想知道微性能的损失,这是一个经典。

如果你关心性能,那么你最好的办法就是测试它并找出原因。@DavidG我也想到了它,但是我很难定义在不同的硬件执行情况下是否以及如何正确测试它。我读到一些现代处理器的无条件跳跃成本很低。然而,到目前为止我学到的是,最好不要跳转。为什么硬件是一个问题?一个比较性能测试应该有两个相同代码的变体,在同一台机器上运行。通过这种方式,你可以将它们比较在一起,而不管哪个硬件运行它。”大量的GOTOs意味着上下文反复切换。“1)上下文切换不是这样的,2)“GOTOs”(以及所有
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;
}