await Task.WhenAll () оказывается заблокированным - PullRequest
0 голосов
/ 20 февраля 2019

В следующем коде в строке Task.WhenAll возникает тупик:

[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);
        creationTasks.Add(task);
    }
    await Task.WhenAll(creationTasks); // <- deadlock here

    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>();
        }

        return null;
    }
}

Тест xUnit пытается создать тестовые данные асинхронно, вызывая асинхронный метод, который сам await sa отвечает извеб-сервис._httpClient - это реальное HttpClient, созданное из памяти TestServer через TestServer.CreateClient().

При установке точки останова на using line в методе CreateLdapEntryХит дважды.Точка останова при проверке кода состояния никогда не срабатывает.При взломе Task.WhenAll() и проверке creationTasks обе задачи находятся в состоянии WaitingForActivation:

creationTasks
Count = 2
    [0]: Id = 32, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
    [1]: Id = 33, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"

Если вы не используете Task.WhenAll(), а вместо этого ожидаете каждую задачу в отдельности, тупик не возникает:

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

Мне известно, что подобный вопрос был задан и получен ответ, однако примеры кода там используют .Result, а не await Task.WhenAll().

IХотелось бы понять, почему возникает эта тупиковая ситуация при использовании Task.WhenAll().

РЕДАКТИРОВАТЬ: добавлен стек вызовов заблокированных потоков

Not Flagged     3992    11  Worker Thread   Worker Thread   Microsoft.AspNetCore.Routing.dll!Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke
                        [Managed to Native Transition]
                        Microsoft.AspNetCore.Routing.dll!Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext)
                        ShibbolethAttributes.Service.dll!RoleManager.Service.Middleware.ApiKeyHandlerMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context) Line 38
                        Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)
                        System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__7>(ref Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.d__7 stateMachine)
                        Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)
                        Microsoft.AspNetCore.Hosting.dll!Microsoft.AspNetCore.Hosting.Internal.HostingApplication.ProcessRequestAsync(Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context context)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.TestServer.ApplicationWrapper.ProcessRequestAsync(Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context context)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.HttpContextBuilder.SendAsync.AnonymousMethod__0()
                        System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startc__DisplayClass10_0.b__0>d stateMachine)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.HttpContextBuilder.SendAsync.AnonymousMethod__0()
                        System.Private.CoreLib.dll!System.Threading.Tasks.Task.InnerInvoke()
                        System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)
                        System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)
                        System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()

Not Flagged     1496    10  Worker Thread   Worker Thread   Microsoft.AspNetCore.Routing.dll!Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke
                        Microsoft.AspNetCore.Routing.dll!Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext)
                        ShibbolethAttributes.Service.dll!RoleManager.Service.Middleware.ApiKeyHandlerMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context) Line 38
                        Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)
                        System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__7>(ref Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.d__7 stateMachine)
                        Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)
                        Microsoft.AspNetCore.Hosting.dll!Microsoft.AspNetCore.Hosting.Internal.HostingApplication.ProcessRequestAsync(Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context context)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.TestServer.ApplicationWrapper.ProcessRequestAsync(Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context context)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.HttpContextBuilder.SendAsync.AnonymousMethod__0()
                        System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startc__DisplayClass10_0.b__0>d stateMachine)
                        Microsoft.AspNetCore.TestHost.dll!Microsoft.AspNetCore.TestHost.HttpContextBuilder.SendAsync.AnonymousMethod__0()
                        System.Private.CoreLib.dll!System.Threading.Tasks.Task.InnerInvoke()
                        System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)
                        System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)
                        System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()

1 Ответ

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

(Заключение суммировано из комментариев, для любых будущих зрителей).

Вы нигде не блокируете, поэтому единственное возможное объяснение, которое я могу придумать, состоит в том, что по крайней мере один из запросов не завершает.

(Если бы вы синхронно ожидали завершения Task, я вполне мог бы ожидать тупика, поскольку вы не используете ConfigureAwait(false), но поскольку вы когда-либо await выполняли только свои Задачи, япочти уверен, что это не причина).

Учитывая, что ваши запросы успешно завершаются, когда они выполняются по отдельности, это подразумевает, что существует некоторая проблема параллелизма, когда несколько запросов выполняются параллельно, возможно, что-то связанное с _httpClient или сервер, к которому обращаются запросы (если они работают на реальном сервере).

Учитывая, что ваш список задач не показывает ничего интересного, я склонендумать, что один из запросов синхронно заблокировал вызвавший его поток.

Просто посмотрите, имеет ли один из запросов syсинхронно заблокирован.Откройте окно Threads и дважды щелкните каждый поток по очереди.Большинство из них ничего не будут делать, но по крайней мере один из них может запускать ваш код или запускать метод, вызываемый из вашего кода.Посмотрите на стек вызовов, чтобы попытаться выяснить, что происходит.Вы можете дважды щелкнуть записи в стеке вызовов, чтобы проверить переменные в области действия в каждой точке.

...