Task.Wait () в асинхронном методе работает локально, но не через другой метод - PullRequest
1 голос
/ 14 марта 2019

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

Я пытался запустить следующий метод клиентской библиотеки Microsoft Graph:

_graphServiceClient.Users[userPrincipalName].Request().GetAsync()

Следующий подход не сработал, так как зависает после использования await:

async Task<User> GetUser(string userPrincipalName)
{
    User user = await _graphServiceClient.Users[userPrincipalName].Request().GetAsync();
    return user;
}

Следующий подход также не работает, так как он зависает после выполнения runTask.Wait():

User GetUser(string userPrincipalName)
{   
    return (User) GetResult(_graphServiceClient.Users[userPrincipalName].Request().GetAsync());
}

object GetResult<TResult>(Task<TResult> task)
{
    using (task)
    using (var runTask = Task.Run(async () => await task))
    {
        try
        {
            runTask.Wait();
            return runTask.Result;
        }
        catch (AggregateException e)
        {
            throw e.InnerException ?? e;
        }
    }
}

Здесь все становится странно, так как следующий код работал:

User GetUser(string userPrincipalName)
{   
    using (var task = Task.Run(async () => await _graphServiceClient.Users[userPrincipalName].Request().GetAsync()))
    {
        try
        {
            task.Wait();
            return task.Result;
        }
        catch (AggregateException e)
        {
            throw e.InnerException ?? e;
        }
    }
}

Хотя я понимаю, что последние два подхода не считаются наилучшей практикой, я полностью озадачен тем, почему третий подход работает, а второй - нет. Мне кажется, они почти идентичны.

Так почему третий метод работает, а не второй?

1 Ответ

4 голосов
/ 14 марта 2019

Во-первых, вам нужно понять , как происходит тупик . await (по умолчанию) захватывает «контекст» и возобновляет выполнение оставшейся части метода async в этом контексте . В случае ASP.NET Classic этот «контекст» является контекстом запроса , который позволяет запускать одновременно только один поток . При работе в потоке пула потоков «контекст» является контекстом пула потоков, который просто ставит в очередь остаток метода async в пул потоков, где он может быть запущен любым потоком пула потоков.

Блокировка асинхронного кода является антипаттерном . В ситуациях, когда у вас есть однопоточный контекст (например, ASP.NET Classic), вы можете оказаться в тупике. В вашем первом примере взаимоблокировки у вас есть GetUser, вызываемый в контексте ASP.NET Classic, поэтому его await будет захватывать этот контекст. Затем вызывающий код (также работающий в контексте ASP.NET Classic) заблокирует эту задачу. Это блокирует поток в этом контексте, что предотвращает завершение GetUser, поэтому вы в конечном итоге зашли в тупик.

Но даже если вы не окажетесь в тупике, вы все равно в конечном итоге отбросите все преимущества асинхронного кода. Рассмотрим «рабочий» пример, который относится к другому antipattern (Task.Run в ASP.NET) . В этом случае Task.Run заставляет GetAsync работать в контексте пула потоков, поэтому блокировка потока в контексте ASP.NET Classic не блокируется. Впрочем, это тоже антипаттерн.

Правильное решение - пройти async до конца . В частности, действие вашего контроллера должно быть async и использовать await при вызове асинхронного кода. Просто представьте, что Wait() и Result не существует, и ваш код будет намного счастливее.

...