Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async await blocking ui wp8

As far as I know the async await keywords work this way: when an await keyword is encountered by the compiler, it suspends the execution of the async function to the caller (in this case the dispatcher), waits for the awaitable function to complete in the background and then then returns back to the function. Am I right?

here is what I am doing:

async private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
     var p = await query("select* from notes", con);
}   

and here is the function being called

 async Task<List<notes>> query(string sqlstring, SQLiteConnection con)
 {
       var p = con.Query<notes>(sqlstring);
       Thread.Sleep(5000);
       MessageBox.Show(p[0].Data); 
       return p;
 }

This blocks the UI thread. why is this happening shouldn't the control be transferred back to the dispatcher when it encounters the await keyword? If so why does the UI get stuck?

like image 422
B0rn2C0de Avatar asked Aug 13 '14 20:08

B0rn2C0de


1 Answers

Your understanding of await is mostly correct, it's just incomplete. await doesn't suspend execution of the current method immediately. First, the value to be awaited needs to be resolved to a value. Typically you'll have some sort of method that creates a Task. That task will be created synchronously, and then after creating that task, execution of the current method will end, and a continuation will be applied to that task that executes the remainder of the method.

For an async method everything before the first await will be performed synchronously by the caller. It is when the first await (or the end of the method, or an exception that isn't caught by the method) that causes the method to return an actual Task.

Your query method should be able to create the Task that represents the completion of the operation very quickly and then return, so that the caller can await that Task. But it doesn't, it spends over 5 seconds creating the task that represents when it will finish, and when it ends up giving that task to its caller that Task has already finished.

This means that Button_Tap can't yield to its caller (which is the UI's message loop) until after query has finished its 5 seconds of task creation.

All of this means that for Button_Tap to be asynchronous query needs to be asynchronous, and not asynchronous in that it uses the async keyword, but asynchronous in the sense that the method returns almost immediately when called, and that it performs its work after yielding control back to the caller.

As to how to make the method asynchronous, that'll depend. For IO, such as querying a database, what you really want is to have the query provider itself provide inherently asynchronous support. You want to have a query method that returns a Task that you can await, rather than synchronously returning its results. If you have no other choice, then as a last resort you can use Task.Run to perform the query in a non-UI thread and then await that.

As for the Thread.Sleep, that is also synchronously blocking the thread. You need to asynchronously do something after a set period of time instead (if you really do need the delay). You can use Task.Delay for that.

async Task<List<notes>> query(string sqlstring, SQLiteConnection con)
{
    var p = await con.QueryAsync<notes>(sqlstring);
    //Or, if there is no asynchronous query method
    //var p = await Task.Run(() => con.Query<notes>(sqlstring));
    await Task.Delay(5000);
    MessageBox.Show(p[0].Data);
    return p;
}

On a side note, you really shouldn't be trying to re-use your database connections like this. They aren't designed to be used concurrently, for starters. Really they're inherently designed to perform just a single operation. You should be creating your connection right when you need it, and cleaning it up as soon as that one operation has been performed.

like image 167
Servy Avatar answered Sep 25 '22 11:09

Servy