有一些文章表明异步数据库调用在 .NET 中是个坏主意。
在 C# 异步 CTP 上,有一个名为
System.Data.SqlClient.SqlCommand
的 ExecuteReaderAsync
扩展。我对现有代码进行了如下一些操作:
var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["hubConnectionString"].ConnectionString;
using (var conn = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand()) {
cmd.Connection = conn;
cmd.CommandText = "sp$DetailsTagsGetAllFromApprovedPropsWithCount";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
var reader = cmd.ExecuteReader();
while (reader.Read()) {
//do the reading
}
conn.Close();
}
}
我的代码中有几个这样的操作。所以,我正在考虑将它们转换为异步。
但另一方面,我并没有看到这种方法有多大吸引力(也许我没有看对正确的方向,谁知道呢!)。
那么,在这里使用这种新的异步编程模型有什么缺点吗?
编辑:
假设我重构代码如下:
public async Task<IEnumerable<Foo>> GetDataAsync() {
List<Foo> foos = new List<Foo>();
var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["hubConnectionString"].ConnectionString;
using (var conn = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand()) {
cmd.Connection = conn;
cmd.CommandText = "sp$DetailsTagsGetAllFromApprovedPropsWithCount";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
var reader = await cmd.ExecuteReaderAsync();
while (reader.Read()) {
//do the reading
//create foos
}
conn.Close();
}
}
return foos;
}
据我从await关键字的理解,它将其后面的代码转换为延续。此外,当它命中await关键字时,无论操作状态如何,它都会立即返回到其调用者。当它完成时,它会返回并触发继续代码。
这就是我的想法。
我不同意 Ricka 的观点。异步数据库命令不仅很好,而且对于实现规模、吞吐量和延迟至关重要。他对线程池加速时间的反对意见仅适用于流量较低的 Web 服务器。
在高流量情况下(这是唯一重要的情况),线程池不必等待“注入”新线程。异步执行 SQL 命令不仅从 Web 服务器请求/线程运行状况的角度来看很重要,而且从总请求生命周期/延迟的角度来看也很重要:不相关的数据库调用可以并行执行,而不是顺序执行。仅此一项通常就会显着改善用户体验到的 HTTP 请求延迟。换句话说,您的页面加载速度更快。
Asynchronous Processing=true
之前,SQL 命令并不是真正的异步。虽然未设置此设置(默认情况下未设置,编辑:不再需要从 .NET Framework 开始< 4.5. Asynchronous Processing
),但对 BeginExecuteReader
的“异步”调用只不过是一个骗局,该调用将启动一个线程并阻止that线程。当在连接字符串中启用真正的异步处理时,调用是真正的异步,并且回调基于 IO 完成。 请注意:一旦 first 结果返回到客户端,异步 SQL 命令就会完成,并且信息消息将计为结果。
create procedure usp_DetailsTagsGetAllFromApprovedPropsWithCount
as
begin
print 'Hello';
select complex query;
end
您已经失去了异步的所有好处。
print
创建一个发送回客户端的结果,该结果完成异步命令并在客户端上恢复执行并继续“reader.Read()”。现在that
将阻塞,直到复杂查询开始产生结果。你问
'谁把
print
放入程序中?' 但是 print
可能会伪装成其他东西,也许看起来像
INSERT
一样无辜,执行 而不需要首先发出
SET NOCOUNT ON
.
我注意到以下问题没有得到解答:
非常小缺点
是
(小CPU/小内存),是因为在await语句之后运行的任何代码都可能在单独的线程上运行,因此存在状态机存储当前正在运行的线程的状态,以便可以在另一个线程上处理继续工作。更多关于 await/async 状态机的内容可以在 Dixin 博客 - 理解 C# async/await(一) 编译。 雷姆斯写道:
您已经失去了异步的所有好处。打印产生的结果是 被发送回客户端,客户端完成异步命令并 客户端上的执行恢复并继续 '读者.Read()'。现在它将阻塞,直到复杂的查询开始 产生结果。您问“谁在程序中打印?”但是 印刷品可能会伪装成其他东西,也许是 无辜的看起来像一个不首先发出一个执行的 INSERT 不设置计数。现在我们已经到了.NET 8,这种情况似乎仍然存在。有解决方法吗?异步调用产生许多结果(包括通知客户端执行状态的消息)的存储过程的正确方法是什么?
例如:
declare @K int=0, @Progress varchar(255)
while @K<100
begin
set @K=@K+1
select Flush=1
set @Progress=concat('Executing ', @K, ' of 100')
raiserror(@Progress, 9, 1) with nowait
waitfor delay '00:00:01'
end
马克。