简体   繁体   English

Microsoft.Graph GetAsync() 无限期挂起

[英]Microsoft.Graph GetAsync() hangs indefinitely

Introduction介绍

I am developing an ASP.NET application which, among other things, is supposed to retrieve users from Azure Active Directory.我正在开发一个 ASP.NET 应用程序,除其他外,它应该从 Azure Active Directory 中检索用户。 For this purpose, I am using the Microsoft Graph version 1.14.0 preview library, which can be found here .为此,我使用的是 Microsoft Graph 1.14.0 版预览库,可在 此处找到。

As this library only provides asynchronous methods for retrieving users, I am using the following (pseudo) code to run it synchronously.由于此库仅提供用于检索用户的异步方法,因此我使用以下(伪)代码来同步运行它。

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;

Problem问题

The problem I am facing right now is that upon calling this piece of code from the ASP.NET application, task.IsCompleted remains forever false .我现在面临的问题是,在从 ASP.NET 应用程序调用这段代码时, task.IsCompleted永远保持false Now here is the strange part which I can not wrap my head around: the code runs perfectly in both a Console Application and a Unit Test (using NUnit).现在这是我无法理解的奇怪部分:代码在控制台应用程序和单元测试(使用 NUnit)中运行完美。

One might think that the GraphServiceClient instance is built differently in these versions, but I am 100% positive that it is not.有人可能认为 GraphServiceClient 实例在这些版本中的构建方式不同,但我 100% 肯定事实并非如此。 The information that makes it up is loaded from a database, and the code in the Unit Test is exactly the same as the code in the controller of the ASP.NET application.组成它的信息是从数据库加载的,单元测试中的代码与ASP.NET应用程序的controller中的代码完全相同 Using the Unit Test, the above code is executed in about 1.5 seconds.使用单元测试,上述代码的执行时间约为 1.5 秒。 In the ASP.NET application, I left it running for as long as 30 minutes without any results, no errors, no time-outs, nothing at all.在 ASP.NET 应用程序中,我让它运行了长达 30 分钟,没有任何结果,没有错误,没有超时,什么也没有。

I realize this might be a bit of a niche problem, but I do hope that someone has run into the same problem and was able to resolve it.我意识到这可能是一个小众问题,但我确实希望有人遇到过同样的问题并能够解决它。

Update更新

I managed to resolve this issue.我设法解决了这个问题。 Weirdly enough, converting all my methods to async Tasks did not work, as even await kept hanging.奇怪的是,将我所有的方法转换为异步任务都没有用,因为甚至await一直挂起。 I do however not fully understand why my solution works now.但是,我不完全理解为什么我的解决方案现在有效。 It looks as though my pseudo-code was not fully accurate, and the solution lies therein.看起来我的伪代码并不完全准确,解决方案就在其中。

Attempt #1 (does not work)尝试#1(不起作用)

This code remains forever in while (.runTask.IsCompleted) .此代码将永远保留在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());
}

Attempt #2 (does not work)尝试#2(不起作用)

This method keeps hanging after executing the await line.此方法在执行await行后一直挂起。

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

Attempt #3 (works)尝试#3(有效)

This code is basically the same as the code in attempt #1, the only difference being that it does not use the GetResult method, but it does use the exact same approach as GetResult .此代码与尝试 #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;
    }
}

While this approach might not be considered best practice, it does work.虽然这种方法可能不被认为是最佳实践,但它确实有效。 I am extremely puzzled about why this approach works, because the code in attempt #1 does not, and it is virtually the same code.我对为什么这种方法有效感到非常困惑,因为尝试 #1 中的代码没有,而且它实际上是相同的代码。 Can anybody explain why that is?谁能解释这是为什么?

I had the same issue ( see here ).我有同样的问题( 见这里)。 I solved it by reverting Microsoft.Graph and Microsoft.Graph.Core version 1.12.0.我通过恢复Microsoft.GraphMicrosoft.Graph.Core版本 1.12.0 解决了这个问题。

The short answer is to make your method async and do this:简短的回答是使您的方法async并执行以下操作:

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

Any time you use .Result (commonly called "sync over async"), you run the risk of deadlocking if you're not very careful.任何时候使用.Result (通常称为“异步同步”),如果您不小心,就会面临死锁的风险。 Deadlocking means two tasks are waiting for each other to finish, which then means that nothing happens.死锁意味着两个任务都在等待对方完成,这意味着什么也没有发生。

In ASP.NET especially, you're far better off using async / await all the way: use it in this method, all the way up to your controller.特别是在 ASP.NET 中,你最好一直使用async / await :在这个方法中使用它,一直到你的控制器。 It's:它的:

  1. Simpler for the developer to read and write开发人员读写更简单
  2. Better for performance since ASP.NET can go do something else with the thread while it's awaiting, instead of blocking the thread (ASP.NET has limited threads)更好的性能,因为 ASP.NET 可以在线程等待时用它做其他事情,而不是阻塞线程(ASP.NET 的线程有限)
  3. You avoid deadlocks你避免死锁

If you want to get into the nitty-gritty and know exactly why deadlocks happen, Stephen Cleary has a great article on it: Don't Block on Async Code如果您想深入了解细节并确切了解死锁发生的原因,Stephen Cleary 有一篇关于它的精彩文章: Don't Block on Async Code

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

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

I get that this absolutely should not be done under 99.9% of circumstances.我知道在 99.9% 的情况下绝对不应该这样做。 But there are times when you dont have a choice.但有些时候你别无选择。 For example in Sitefinity you make widgets via controller classes.例如,在 Sitefinity 中,您通过 controller 类制作小部件。 These controller classes do not support async actions (Sitefinity does not support this) and the microsoft graph sdk only gives you an async option to get data.这些 controller 类不支持异步操作(Sitefinity 不支持此操作)并且 microsoft graph sdk 只为您提供异步选项来获取数据。 So I have to find a way to make that async, synchronous instead.所以我必须找到一种方法来使异步变为同步。 So for me, I had to do this nasty workaround.所以对我来说,我不得不做这个讨厌的解决方法。 The key is ConfigureAwait(false).关键是 ConfigureAwait(false)。 It prevents the deadlock ( https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html ).它可以防止死锁 ( https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html )。 I hope it helps someone who is stuck in the same position as me.我希望它能帮助那些和我一样被困在同一个 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