Microsoft.Graph GetAsync () висит бесконечно - PullRequest
1 голос
/ 11 марта 2019

Введение

Я занимаюсь разработкой приложения ASP.NET, которое, помимо прочего, должно извлекать пользователей из Azure Active Directory. Для этой цели я использую библиотеку предварительного просмотра Microsoft Graph версии 1.14.0, которую можно найти здесь .

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

string userPrincipalName = "test.user@intuneforeducation.com";
var task = Task.Run(async () => await _graphServiceClient.Users[userPrincipalName].Request().GetAsync());

while (!task.IsCompleted)
     Thread.Sleep(200);

User retrievedUser = task.Result;

Задача

Проблема, с которой я сейчас сталкиваюсь, заключается в том, что при вызове этого фрагмента кода из приложения ASP.NET task.IsCompleted остается навсегда false. Теперь вот странная часть, которую я не могу обернуть: код отлично работает как в консольном приложении, так и в модульном тесте (с использованием NUnit).

Можно подумать, что экземпляр GraphServiceClient в этих версиях построен по-другому, но я на 100% уверен, что это не так. Информация, из которой он состоит, загружается из базы данных, и код в модульном тесте точно такой же, как код в контроллере приложения ASP.NET. Используя модульный тест, приведенный выше код выполняется примерно за 1,5 секунды. В приложении ASP.NET я оставлял его запущенным в течение 30 минут без каких-либо результатов, без ошибок, без тайм-аутов, вообще ничего.

Я понимаю, что это может быть проблемой ниши, но я надеюсь, что кто-то столкнулся с той же проблемой и смог ее решить.

Обновление

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

Попытка # 1 (не работает)

Этот код остается навсегда в while (!runTask.IsCompleted).

object GetResult<TResult>(Task<TResult> task)
{
    using (task)
    using (var runTask = Task.Run(async () => await task))
    {
        while (!runTask.IsCompleted)
            Thread.Sleep(SleepTime);

        if (runTask.Exception != null)
            throw runTask.Exception.InnerException ?? runTask.Exception;

        return runTask.Result;
    }
}

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

Попытка # 2 (не работает)

Этот метод продолжает зависать после выполнения строки await.

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

Попытка № 3 (работает)

Этот код в основном совпадает с кодом в попытке № 1, с той лишь разницей, что он не использует метод GetResult, но он использует тот же подход, что и GetResult.

User GetUser(string userPrincipalName)
{
    using(var task = Task.Run(async () => await _graphServiceClient.Users[userPrincipalName].Request().GetAsync()))
    {
        while (!task.IsCompleted)
            Thread.Sleep(200);

        return task.Result;
    }
}

Хотя этот подход не может считаться лучшей практикой, он работает. Я чрезвычайно озадачен тем, почему этот подход работает, потому что код в попытке № 1 нет, и это практически тот же код. Кто-нибудь может объяснить, почему это так?

Ответы [ 2 ]

0 голосов
/ 02 мая 2019

У меня была такая же проблема ( см. Здесь ). Я решил это, вернув Microsoft.Graph и Microsoft.Graph.Core версию 1.12.0.

0 голосов
/ 11 марта 2019

Краткий ответ - сделать ваш метод async и сделать это:

string userPrincipalName = "test.user@intuneforeducation.com";
User retrievedUser = await _graphServiceClient.Users[userPrincipalName].Request().GetAsync();

Каждый раз, когда вы используете .Result (обычно называемый «синхронизация через асинхронную синхронизацию»), вы рискуете получить взаимоблокировку, если не будете очень осторожны. Блокировка означает, что две задачи ждут завершения друг друга, что означает, что ничего не происходит.

Особенно в ASP.NET вам гораздо лучше использовать async / await полностью: используйте его в этом методе, вплоть до вашего контроллера. Это:

  1. Проще для разработчика читать и писать
  2. Лучше для производительности, так как ASP.NET может делать что-то еще с потоком, пока он ожидает, вместо того, чтобы блокировать поток (ASP.NET имеет ограниченные потоки)
  3. Вы избегаете тупиков

Если вы хотите разобраться в мелочах и точно знать, почему возникают взаимоблокировки, у Стивена Клири есть отличная статья: Не блокировать асинхронный код

...