为什么Task.WaitAll()不会阻塞或导致死锁?

Kru*_*lur 11 c# task-parallel-library async-await xamarin

在下面的示例中await,使用了两个调用.为了获得性能,样本会被转换Task.WaitAll()(实际上并不是更快,但这只是一个例子).

这是来自Android上使用Sqlite.Net的库中的代码,该方法从OnResume()主UI线程调用:

public async Task SetupDatabaseAsync()
{
  await CreateTableAsync<Session>();
  await CreateTableAsync<Speaker>();
}
Run Code Online (Sandbox Code Playgroud)

这是替代方案:

public void SetupDatabaseAsync()
{
  var t1 = CreateTableAsync<Session>();
  var t2 = CreateTableAsync<Speaker>();

  Task.WaitAll(t1, t2);
}
Run Code Online (Sandbox Code Playgroud)

但是从我的理解Task.WaitAll()应该在等待时阻止UI线程,从而导致死锁.但它的工作正常.那是因为这两个调用实际上并没有在UI线程上调用任何东西吗?

如果我使用它有什么区别Task.WhenAll()?我猜它即使会调用UI线程也能工作,就像使用它一样await.

Ste*_*ary 16

我在博客上描述了死锁情况细节.我也有一篇MSDN文章SynchronizationContext,你可能会觉得有帮助.

总之,Task.WaitAll在您的方案中会出现死锁,但前提是任务需要同步回UI线程才能完成.您可以得出结论,CreateTableAsync<T>()不会同步回UI线程.

相反,这段代码会死锁:

public async Task SetupDatabaseAsync()
{
  await CreateTableAsync<Session>();
  await CreateTableAsync<Speaker>();
}

Task.WaitAll(SetupDatabaseAsync());
Run Code Online (Sandbox Code Playgroud)

我建议你不要阻止异步代码; 在async世界上,同步回到上下文是默认行为(正如我在我的async介绍中描述的那样),因此很容易意外地做到这一点.未来对Sqlite.Net的一些更改可能(意外地)同步回原始上下文,然后使用Task.WaitAll与原始示例类似的任何代码将突然死锁.

最好使用async"一路":

public Task SetupDatabaseAsync()
{
  var t1 = CreateTableAsync<Session>();
  var t2 = CreateTableAsync<Speaker>();
  return Task.WhenAll(t1, t2);
}
Run Code Online (Sandbox Code Playgroud)

"异步一直"是我在异步最佳实践文章中推荐的指南之一.