Task.WaitAll () взаимоблокировка - PullRequest
0 голосов
/ 19 февраля 2019

Я хочу несколько раз вызвать асинхронный метод в тесте 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, вызывающего веб-службу, await s ответ и, возможно, await s.чтение содержимого ответа, которое, наконец, десериализовано и возвращено.

Когда я заменяю часть foreach в тесте следующим (то есть, не использую Task.WhenAll() / WaitAll()), код выполняется без тупика:

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

Что именно происходит?

РЕДАКТИРОВАТЬ: Хотя этот вопрос был отмечен как дубликат, я не вижу, как связанный вопрос связан с этим.Все примеры кода в ссылке используют .Result, который, насколько я понимаю, блокирует выполнение до завершения задачи.Напротив, Task.WhenAll() возвращает задачу, которую можно ожидать и которая заканчивается, когда все задачи завершены.Так почему же ожидается Task.WhenAll() взаимоблокировка?

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Код, который вы разместили, не может иметь описанное поведение.Первый вызов 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 "";
}
0 голосов
/ 19 февраля 2019

Task.WaitAll() заблокируется просто потому, что блокирует текущий поток, пока задачи не завершены (и поскольку вы используете async/await , а не потоки , все ваши задачи выполняются в одном потоке, и вы не позволяете своим ожидаемым задачам вернуться к точке вызова, потому что поток, в котором они запущены - тот же, в котором вы вызвали Task.WaitAll() -, заблокирован).

Не уверен, почему WhenAll также блокирует вас здесь, хотя, определенно, не должен.

PS: вам не нужно вызывать Start для задач, возвращаемых методом async: они"горячие" (уже запущены) уже при создании

...