为什么使用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关键字

为什么使用Dapper QueryAsync<T>没有异步/等待

第一个方法启动查询(调用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下面的代码)中使用它们。这类似于在幕后实现闭包