为什么使用Dapper QueryAsync没有异步/等待
本文关键字:异步 等待 Dapper QueryAsync 为什么 | 更新日期: 2023-09-27 18:05:08
为什么在这个方法上调用.Result
会导致TaskCanceledException:
public Task<IEnumerable<object>> GetAsync()
{
using (var conn = new SqlConnection("connectionString"))
{
return conn.QueryAsync<object>("select * from objects");
}
}
但是在这个方法上调用.Result
是有效的:
public async Task<IEnumerable<object>> GetAsync()
{
using (var conn = new SqlConnection("connectionString"))
{
return await conn.QueryAsync<object>("select * from objects");
}
}
不同之处在于第二种方法中使用了async'await
关键字
第一个方法启动查询(调用QueryAsync
),然后处置SqlConnection
,然后返回表示该查询的任务。任务被取消,因为SqlConnection
在它完成之前被处理掉了。
第二个方法启动查询(调用QueryAsync
),异步等待该查询完成,然后然后处置SqlConnection
。从第二个方法返回的任务表示该方法的完成。
有关async
/await
的更多信息,请参阅我的博客。
顺便说一句,你不应该使用Result
使用异步方法;你应该使用await
第一个方法抛出异常的原因是当从该方法返回Task时,SqlConnection对象超出了作用域。
第二个方法有效的原因是async &Await关键字模拟了一个闭包,因为编译器在后台将该方法包装在一个类似状态机的结构中。这最终使SqlConnection
保持在作用域中。我就不多说了,因为细节有点复杂,而且根据编译器的不同而不同。
为了能够返回一个没有async/await关键字的Task,您需要在SqlConnection中传递它,以便它在返回Task时保持在作用域中:
public Task<IEnumerable<object>> GetAsync(SqlConnection conn)
{
return conn.QueryAsync<object>("select * from objects");
}
更新#1(回应评论)
为什么与作用域和闭包有关:
在using
语句中实例化SqlConnection
将其作用域限制为using
块。因此,当第一个方法离开using
块并返回Task时,SqlConnection
将离开作用域并在Task完成之前被处理掉。在第二个方法中,async/await
关键字将导致编译器在后台创建一个struct
,并将异步方法的局部变量存储为该结构体中的字段,以便在Task完成时在回调(await下面的代码)中使用它们。这类似于在幕后实现闭包。