繁体   English   中英

Microsoft.Graph GetAsync() 无限期挂起

[英]Microsoft.Graph GetAsync() hangs indefinitely

介绍

我正在开发一个 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应用程序的controller中的代码完全相同 使用单元测试,上述代码的执行时间约为 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 中的代码没有,而且它实际上是相同的代码。 谁能解释这是为什么?

我有同样的问题( 见这里)。 我通过恢复Microsoft.GraphMicrosoft.Graph.Core版本 1.12.0 解决了这个问题。

简短的回答是使您的方法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

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

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

我知道在 99.9% 的情况下绝对不应该这样做。 但有些时候你别无选择。 例如,在 Sitefinity 中,您通过 controller 类制作小部件。 这些 controller 类不支持异步操作(Sitefinity 不支持此操作)并且 microsoft graph sdk 只为您提供异步选项来获取数据。 所以我必须找到一种方法来使异步变为同步。 所以对我来说,我不得不做这个讨厌的解决方法。 关键是 ConfigureAwait(false)。 它可以防止死锁 ( https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html )。 我希望它能帮助那些和我一样被困在同一个 position 的人。

public IList<UserModel> GetAllUsers()
{
    var client = _graphClientFactory.GetClient();
    // This bad looking hack is to be able to call the async method synchronously without deadlocking.
    // It is necessary because Sitefinity does not support async controller actions...
    var task = Task.Run(() =>
    {
        return client.Users.Request().GetAsync();
    }).ConfigureAwait(false);
    var result = task.GetAwaiter().GetResult();
    // Build models
    return new List<UserModel>();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM