Task.WaitAll()死锁

时间:2019-02-19 14:50:56

标签: c# async-await task

我想在xUnit测试中多次调用异步方法,并等待所有调用完成后再继续执行。我读到我可以在这种情况下使用Task.WhenAll()Task.WaitAll()。但是由于某种原因,代码陷入僵局。

[Fact]
public async Task GetLdapEntries_ReturnsLdapEntries()
{
    var ldapEntries = _fixture.CreateMany<LdapEntryDto>(2).ToList();
    var creationTasks = new List<Task>();
    foreach (var led in ldapEntries)
    {
        var task = _attributesServiceClient.CreateLdapEntry(led);
        task.Start();
        creationTasks.Add(task);
    }
    Task.WaitAll(creationTasks.ToArray()); //<-- deadlock(?) here
    //await Task.WhenAll(creationTasks);

    var result = await _ldapAccess.GetLdapEntries();

    result.Should().BeEquivalentTo(ldapEntries);
}

public async Task<LdapEntryDto> CreateLdapEntry(LdapEntryDto ldapEntryDto)
{
    using (var creationResponse = await _httpClient.PostAsJsonAsync<LdapEntryDto>("", ldapEntryDto))
    {
        if (creationResponse.StatusCode == HttpStatusCode.Created)
        {
            return await creationResponse.Content.ReadAsAsync<LdapEntryDto>();
        }

        throw await buildException(creationResponse);
    }
}

受测系统是HttpClient的包装,该包装调用Web服务,await s响应,并且可能await读取响应的内容,并最终反序列化并返回

当我将测试中的foreach部分更改为以下内容(即,不使用Task.WhenAll() / WaitAll())时,代码正在运行而没有死锁:

foreach (var led in ldapEntries)
{
    await _attributesServiceClient.CreateLdapEntry(led);
}

到底发生了什么?

编辑:虽然此问题被标记为重复,但我看不到链接的问题与该问题的关系。链接中的所有代码示例均使用.Result,据我所知,它会阻塞执行直到任务完成。相反,Task.WhenAll()返回的任务可以等待,并且在所有任务完成后才完成。那么为什么要等待Task.WhenAll()陷入僵局?

2 个答案:

答案 0 :(得分:1)

您发布的代码可能无法描述行为。对Task.Start的第一次调用会抛出InvalidOperationException,但测试失败。

  

我了解到我可以在这种情况下使用Task.WhenAll()和Task.WaitAll()。

否;要异步地等待多个任务,必须使用Task.WhenAll,而不是Task.WaitAll

示例:

[Fact]
public async Task GetLdapEntries_ReturnsLdapEntries()
{
  var ldapEntries = new List<int> { 0, 1 };
  var creationTasks = new List<Task>();
  foreach (var led in ldapEntries)
  {
    var task = CreateLdapEntry(led);
    creationTasks.Add(task);
  }
  await Task.WhenAll(creationTasks);
}

public async Task<string> CreateLdapEntry(int ldapEntryDto)
{
  await Task.Delay(500);
  return "";
}

答案 1 :(得分:0)

Task.WaitAll()会死锁的原因仅仅是因为它在任务未完成时阻塞了当前线程(并且由于您使用的是async/await 而非线程,因此所有任务都是在同一线程上运行,并且您不让等待的任务返回到调用点,因为它们在-与您调用Task.WaitAll() -相同的线程中运行-阻止)。

虽然不确定WhenAll为何在这里也对您造成僵局,但绝对不应该。

PS:您无需在Start方法返回的任务上调用async:它们在创建时就已经“热”了(已经开始)