【问题标题】:Microsoft.Graph GetAsync() hangs indefinitelyMicrosoft.Graph GetAsync() 无限期挂起
【发布时间】:2019-08-01 23:19:05
【问题描述】:

简介

我正在开发一个 ASP.NET 应用程序,除其他外,它应该从 Azure Active Directory 检索用户。为此,我使用的是 Microsoft Graph 版本 1.14.0 预览库,可以在 here 找到。

由于这个库只提供了检索用户的异步方法,我使用下面的(伪)代码来同步运行它。

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 中的代码没有,而且它实际上是相同的代码。谁能解释这是为什么?

【问题讨论】:

  • 如果您在 ASP.NET 中运行,我不明白您为什么不异步运行所有内容。
  • 另外,你为什么首先将任务包装在任务中?
  • 1.现在我正在避免异步调用,因为我对异步编程不够熟悉,无法在生产环境中使用它。然而,这无关紧要。 2. 这种方法允许我在同步上下文中使用异步调用。因此,我可以在User 类型的方法中使用此代码,而不必使用Task&lt;User&gt;。然而,这也不是重点。
  • 一切都与同步上下文有关 - stackoverflow.com/questions/20765723/…
  • 是的,由于同步上下文,您陷入了僵局。转换为异步 :)

标签: c# azure active-directory microsoft-graph-api


【解决方案1】:

简短的回答是让你的方法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. 避免死锁

如果您想深入了解细节并确切了解为什么会发生死锁,Stephen Cleary 有一篇很棒的文章:Don't Block on Async Code

【讨论】:

    【解决方案2】:

    我遇到了同样的问题 (see here)。我通过恢复 Microsoft.GraphMicrosoft.Graph.Core 版本 1.12.0 解决了这个问题。

    【讨论】:

      【解决方案3】:

      避免在函数中使用Result 但直接与用户打交道

      User user = await gServiceClient.Users[ID].Request().GetAsync();
      

      【讨论】:

        猜你喜欢
        • 2013-11-03
        • 2017-06-29
        • 2011-05-07
        • 2021-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-29
        • 1970-01-01
        相关资源
        最近更新 更多