Dapper QueryAsync 首次阻塞 UI 查询(针对 Oracle 服务器)?

Dapper QueryAsync blocks UI for the first time querying (against Oracle server)?

首先我认为第一次只是为了更清楚地看到这个阻塞的条件。对于下一次,它仍然会以某种方式稍微阻止 UI 但不像不使用异步时那样明显。

我可以这么说,因为我可以看到使用 QueryAsync 和使用 Task.Run(() => connection.Query<T>) 的简单包装代码之间的区别,后者工作正常,当然比 QueryAsync 好得多(在用户体验).

代码就这么简单:

public async Task<IEnumerable<Item>> LoadItemsAsync(){
  using(var con = new OracleConnection(connectionString)){
     var items = await con.QueryAsync<dynamic>("someQuery");
     return items.Select(e => new Item { ... });
  }
}
//in UI thread, load items like this:
var items = await LoadItemsAsync();

工作正常的代码(没有阻塞UI)是这样的:

public async Task<IEnumerable<Item>> LoadItemsAsync(){
  using(var con = new OracleConnection(connectionString)){
     var items = await Task.Run(() => con.Query<dynamic>("someQuery"));
     return items.Select(e => new Item { ... });
  }
}
//in UI thread, load items like this:
var items = await LoadItemsAsync();

我知道 Task.Run() 实际上并不是异步 细节 但至少它将整个工作放到另一个线程并使 UI 空闲免于被封锁和冻结。

我想这可能是 Dapper 中的一个错误,请花点时间测试一下。我不太确定如何准确地重现它,但如果可能的话,请尝试一个 Winforms project,一个相当大的 Oracle 数据库,当然正如我所说,你可以在第一次查询时最明显地看到它(所以确保 运行 每次测试前针对 Oracle 服务器的清除缓存查询。

最后,如果您对此有一些解释和解决方案(当然不使用Task.Run),请分享您的答案。

使用 async await,您可以仅在执行真正的异步操作(例如,异步 IO 或委托给线程池线程的任务)期间释放和使用 UI 线程。在您的情况下,使用 Oracle 驱动程序 (ODP.NET) 的方法并不是真正的异步方法。请参阅 关于 Stack Overflow 的讨论。

如果您想从 UI 线程卸载工作以提高响应速度,只需使用 Task.Run():

var items = await Task.Run(() => LoadItems());

使用任何其他机制,例如 Task.ConfigureAwait(false),或同步上下文替换与 Task.Yield() 相结合,也会导致使用额外的线程池线程,但它会释放 UI稍后线程。

更多信息请查看:

我认为您在这里看到的可能是 .net 中的错误,而不是 dapper。错误是:

https://github.com/Microsoft/dotnet/issues/579

您可能遇到的问题是 dapper.Query 会在内部调用 SqlDataReader.ReadAsync,这有时会导致 sync/blocking 行为。此错误应在 .net 4.7.3 中修复,但最新的预发布版本似乎仍包含此问题。

另见:How do I make SqlDataReader.ReadAsync() run asynchronously?