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

时间:2014-03-27 20:58:03

标签: c# xamarin task-parallel-library async-await

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

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

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

这是替代方案:

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

  Task.WaitAll(t1, t2);
}

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

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

3 个答案:

答案 0 :(得分:15)

我在博客上描述了details of the deadlock situation。我也有MSDN article on SynchronizationContext您可能会觉得有帮助。

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

相反,这段代码会死锁:

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

Task.WaitAll(SetupDatabaseAsync());

我建议您阻止异步代码;在async世界中,同步回复上下文是默认行为(正如我在async intro中所述),因此很容易不小心做了。未来对Sqlite.Net的一些更改可能(意外地)同步回原始上下文,然后使用Task.WaitAll的任何代码都会像原始示例一样突然死锁。

最好一直使用async&#34;&#34;:

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

&#34;一路异步&#34;是我在asynchronous best practices article中推荐的指南之一。

答案 1 :(得分:0)

当您阻止UI线程(以及当前的同步上下文)时,如果您正在等待的其中一个任务将委托给当前上下文然后等待,那么它只会导致死锁它(同步或异步)。在任何异步方法上同步阻塞并不是每个案例中的即时死锁。

因为默认情况下,async方法会将方法的其余部分封送到当前同步上下文和每个await之后,并且因为任务将永远完成,直到发生这种情况,这意味着同步等待使用async/await的方法通常会死锁;至少除非明确覆盖所描述的行为(通过,例如ConfigureAwait(false))。

使用WhenAll表示您没有阻止当前的同步上下文。所有其他任务完成后,您只需要安排另一个继续运行,而不是阻止该线程,让上下文自由处理任何其他已准备就绪的请求(比如说, ,async等待的基础WhenAll方法的延续。

答案 2 :(得分:-1)

也许这个样本将展示可能发生的事情。这是一个iOS视图加载。尝试使用await调用和不使用它(下面已注释掉)。在函数中没有任何等待它将同步运行并且UI将被阻止。

    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?";
    }