C# 读取DbDataReader时的异步/等待效率(或滥用)
偶然发现了一段比较常用的代码,起初似乎效率很低。(我知道优化有时可能是邪恶的,但我想知道) 简介部分-相当简单的SP执行+读取返回的数据: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
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;
}