In the example below two await
calls are used. To gain performance, the sample gets converted Task.WaitAll()
instead (not really any faster, but this is just an example).
This is code from a library using Sqlite.Net on Android and the method gets called from OnResume()
on the main UI thread:
public async Task SetupDatabaseAsync()
{
await CreateTableAsync<Session>();
await CreateTableAsync<Speaker>();
}
Here's the alternative:
public void SetupDatabaseAsync()
{
var t1 = CreateTableAsync<Session>();
var t2 = CreateTableAsync<Speaker>();
Task.WaitAll(t1, t2);
}
But from my understanding Task.WaitAll()
should block the UI thread while waiting, thus leading to a deadlock. But it works just fine. Is that because the two calls don't actually invoke anything on the UI thread?
What's the difference if I use Task.WhenAll()
instead? My guess it that it would work even if the UI thread would be invoked, just like with await
.
WhenAll we will get a task object that isn't complete. However, it will not block but will allow the program to execute. On the contrary, the Task. WaitAll method call actually blocks and waits for all other tasks to complete.
Task. WhenAll creates a task that will complete when all of the supplied tasks have been completed. It's pretty straightforward what this method does, it simply receives a list of Tasks and returns a Task when all of the received Tasks completes.
The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.
If you block the UI thread there is nothing left to execute tasks and you have a deadlock. If you're writing a dotnet core web application, you're basically running everything on the thread pool. Any blocking code will block the thread pool and any . Result will lead to a deadlock.
I describe the details of the deadlock situation on my blog. I also have an MSDN article on SynchronizationContext
that you may find helpful.
In summary, Task.WaitAll
will deadlock in your scenario, but only if the tasks need to sync back to the UI thread in order to complete. You can conclude that CreateTableAsync<T>()
does not sync back to the UI thread.
In contrast, this code will deadlock:
public async Task SetupDatabaseAsync()
{
await CreateTableAsync<Session>();
await CreateTableAsync<Speaker>();
}
Task.WaitAll(SetupDatabaseAsync());
I recommend that you not block on asynchronous code; in the async
world, sync'ing back to the context is the default behavior (as I describe in my async
intro), so it's easy to accidentally do it. Some changes to Sqlite.Net in the future may (accidentally) sync back to the original context, and then any code using Task.WaitAll
like your original example will suddenly deadlock.
It's best to use async
"all the way":
public Task SetupDatabaseAsync()
{
var t1 = CreateTableAsync<Session>();
var t2 = CreateTableAsync<Speaker>();
return Task.WhenAll(t1, t2);
}
"Async all the way" is one of the guidelines I recommend in my asynchronous best practices article.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With