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



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

介绍部分-相当简单的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(读取器)

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;
}

正如你所看到的,有很多等待调用,编译器会把它们变成状态机,不是吗?

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

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


我的问题是

1) 这种滥用async/await的行为是不是没有明显的原因,因为行数据已经在内存中缓冲了,对吧?

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

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


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

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

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

ADO.NET允许实现者在如何准确实现基类型方面有很大的余地。也许这场争吵在记忆中,也许不是。

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

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

注意,状态机和await在这里实际上不增加开销;有一个异步快速路径,如果可能的话,代码只是保持同步执行。

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

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

但是,即使是这种性能影响,在对数据库服务器的网络I/O调用之后也很可能不会引起注意。也就是说,如果你想知道微观性能的惩罚,异步的禅宗是一个经典。

相关内容

  • 没有找到相关文章

最新更新