Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Firstly I believe that the first time is just a condition to see this blocking more clearly. For next times, somehow it still blocks the UI slightly but not obvious like when not using async.

I can say that because I can see the difference between using that QueryAsync and a simple wrapping code with Task.Run(() => connection.Query<T>) which works fine and of course much better than QueryAsync (in UX).

The code is just simple like this:

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();

The code working fine (without blocking UI) is like this:

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();

I know that Task.Run() is not actually async to the detail but at least it puts the whole work to another thread and makes the UI free from being blocked and frozen.

I guess this might be a bug in Dapper, please take sometime to test this. I'm not so sure how to exactly reproduce this, but if possible please try a Winforms project, a fairly large Oracle database and of course as I said you can see it the most obviously by the first time querying (so be sure to run the clearing-cache query against the Oracle server before each test).

Finally if you have some explanation and solution to this (of course without using Task.Run), please share in your answer.

like image 464
Hopeless Avatar asked Aug 02 '18 04:08

Hopeless


1 Answers

With async await you can free and use UI thread only during execution of a truly asynchronous operation (for example, asynchronous IO or a task delegated to a thread pool thread). In your case, methods that utilze Oracle driver (ODP.NET) are not truly asynchronous. See Can the Oracle Managed Driver use async/wait properly? discussion on Stack Overflow.

If you want to offload work from a UI thread to increase responsiveness, simply use Task.Run():

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

Use of any other mechanism such as Task.ConfigureAwait(false), or synchronization context replacement combined with Task.Yield() will result in use of an additional thread pool thread too, but it will free UI thread later.

For more information check:

  • Should I expose asynchronous wrappers for synchronous methods? article by Stephen Toub
  • Async/Await FAQ article by Stephen Toub
  • Task.Run Etiquette Examples: Even in the Complex Case, Don't Use Task.Run in the Implementation article by Stephen Cleary
like image 184
Leonid Vasilev Avatar answered Sep 19 '22 12:09

Leonid Vasilev