Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Task.WaitAll() not block or cause a deadlock here?

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.

like image 907
Krumelur Avatar asked Mar 27 '14 20:03

Krumelur


People also ask

Does task WaitAll block?

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.

How does task WhenAll work?

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.

Does await block the current thread?

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.

What is task deadlock?

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.


1 Answers

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.

like image 153
Stephen Cleary Avatar answered Sep 23 '22 04:09

Stephen Cleary