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

noo*_*bed 4 .net c# asynchronous compiler-generated .net-core-2.1

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

简介部分-相当简单的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();
}
Run Code Online (Sandbox Code Playgroud)

我担心的是功能

等待GetProjectElement(阅读器)

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;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,编译器变成了状态机,有很多等待调用,不是吗?

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

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


我的问题是

1)这种异步/等待的滥用没有明显的原因,因为行数据已经在内存中缓冲了吗?

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

3)与没有awaiting的方法相比,这种方法的性能实际上是否会更差


最后一点:如果我做对的话,我们想对内容可能超过合理长度的大型表行使用CommandBehavior.SequentialAccess,因此我们想异步读取它吗?(如存储varbinary(max)或Blob)

Ste*_*ary 5

正如其他人指出的那样,GOTO不会引起上下文切换,而且速度很快。

1)这种异步/等待的滥用没有明显的原因,因为行数据已经在内存中缓冲了吗?

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

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

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

注意,状态机await在这里几乎不增加任何开销。有一个异步的快速路径,如果可能的话,代码将保持同步执行。

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

可能不会。首先,调用每个方法并继续同步执行所需的很少的CPU工作不会对性能产生影响。您看到的任何性能影响都将归因于Task<T>Gen0堆上抛出的其他实例,这些实例必须进行垃圾回收。这就是现在存在的原因ValueTask<T>

但是,即使对性能产生影响,在对数据库服务器进行网络I / O调用之后,也很可能不会引起注意。就是说,如果您想知道微性能的损失,Async Zen是经典的。