Pergunta

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.

Foi útil?

Solução

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.

Outras dicas

When you're blocking the UI thread (and the current synchronization context) it will only cause a deadlock if one of the tasks that you're waiting on marshals a delegate to the current context and then waits on it (synchronously or asynchronously). Synchronously blocking on any async method isn't an instant deadlock in every single case.

Because async methods will, by default, marshal the remainder of the method to the current synchronization context and after every single await, and because the task will never finish until that happens, it means that synchronously waiting on methods that use async/await will often deadlock; at least unless the described behavior is explicitly overridden (through, say ConfigureAwait(false)).

Using WhenAll means that you're not blocking the current synchronization context. Instead of blocking the thread you're just scheduling another continuation to be run when all of the other tasks finish, leaving the context free to handle any other requests that are ready right now (like, say, the continuation from the underlying async method that WhenAll is waiting on).

Maybe this sample will demonstrate what might be happening. It's an iOS view loading. Try it with both the await call and without it (commented out below). Without any await in the function it will run synchronously and the UI will be blocked.

    public async override void ViewDidLoad()
    {
        base.ViewDidLoad ();

        var d1 = Task.Delay (10);
        var d2 = Task.Delay (10000);

        //await Task.Delay (10);

        Task.WaitAll (d1, d2);

        this.label.Text = "Tasks have ended - really!";
    }

    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear (animated);
        this.label.Text = "Tasks have ended - or have they?";
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top