为什么在没有异步/等待的情况下使用Dapper QueryAsync <T>时抛出TaskCanceledException?

Bra*_*don 4 c# task async-await dapper

为什么调用.Result此方法会导致TaskCanceledException:

public Task<IEnumerable<object>> GetAsync()
{
    using (var conn = new SqlConnection("connectionString"))
    {
        return conn.QueryAsync<object>("select * from objects");
    }
}
Run Code Online (Sandbox Code Playgroud)

但是调用.Result这个方法有效:

public async Task<IEnumerable<object>> GetAsync()
{
    using (var conn = new SqlConnection("connectionString"))
    {
        return await conn.QueryAsync<object>("select * from objects");
    }
}
Run Code Online (Sandbox Code Playgroud)

区别在于async\await第二种方法中使用的是关键字.

Ste*_*ary 7

第一种方法启动查询(调用QueryAsync),然后它处理SqlConnection,然后它返回表示该查询的任务.该任务被取消,因为SqlConnection它在完成之前已被处置.

第二种方法启动查询(调用QueryAsync),异步等待该查询完成,然后处理SqlConnection.从第二个方法返回的任务表示该方法的完成.

有关async/的更多信息await,请参阅我的博客.

另外,您不应该使用异步方法Result; 你应该用await.


Bra*_*don 3

The reason the first method throws an exception is because the SqlConnection object is going out of scope when the Task is returned from the method.

The reason the second method works is because the async & await keywords simulate a closure due to the compiler wrapping the method in a state-machine-like struct behind the scenes. This ends up keeping the SqlConnection in scope. I won't go much further than that because the details are a bit complex and vary based on the compiler.

To make it possible to return a Task without the async/await keywords you'd need to pass in the SqlConnection so it remains in scope upon returning the Task:

public Task<IEnumerable<object>> GetAsync(SqlConnection conn)
{
    return conn.QueryAsync<object>("select * from objects");
}     
Run Code Online (Sandbox Code Playgroud)

Update #1 (in response to comment)

Why it has to do with scope and closures:

SqlConnection在语句中实例化using将其范围限制为using块。因此,当第一个方法离开using块并返回任务时,任务SqlConnection就会超出范围并在任务完成之前被释放。在第二种方法中,关键字将导致编译器在后台async/await创建 a ,并将异步方法的局部变量存储为该结构中的字段,以便在任务完成时可以在回调(await 下面的代码)中使用它们。struct这类似于闭包在幕后的实现方式。