Microsoft.Graph GetAsync() hangs indefinitely

2019-07-23 12:22发布

问题:

Introduction

I am developing an ASP.NET application which, among other things, is supposed to retrieve users from Azure Active Directory. For this purpose, I am using the Microsoft Graph version 1.14.0 preview library, which can be found here.

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. 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).

One might think that the GraphServiceClient instance is built differently in these versions, but I am 100% positive that it is not. 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. Using the Unit Test, the above code is executed in about 1.5 seconds. 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.

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. 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)

This code remains forever in 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)

This method keeps hanging after executing the await line.

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

Attempt #3 (works)

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.

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. Can anybody explain why that is?

回答1:

The short answer is to make your method async and do this:

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. 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. 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)
  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



回答2:

I had the same issue (see here). I solved it by reverting Microsoft.Graph and Microsoft.Graph.Core version 1.12.0.