11

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?

Johan Wintgens
  • 529
  • 7
  • 15
  • If you are running inside ASP.NET I don't understand why you aren't running everything asynchronously. – DavidG Mar 11 '19 at 15:35
  • Also, why are you wrapping a task inside a task in the first place? – DavidG Mar 11 '19 at 15:39
  • 1. For now I am avoiding asynchronous calls, as I am not sufficiently acquainted with asynchronous programming to use it in a production environment. This is beside the point, however. 2. This approach allows me to use the asynchronous calls in a synchronous context. As such, I can use this code in a method of type `User` instead of having to use `Task`. However, this is beside the point as well. – Johan Wintgens Mar 11 '19 at 16:01
  • They are not beside the point though, if you spend a few minutes learning, then you will discover the correct way to do this and this question is no longer relevant. – DavidG Mar 11 '19 at 16:03
  • As mentioned in the original post, the code works in both a Console Application and a Unit Test. How do you suppose this should be different in ASP.NET, though? – Johan Wintgens Mar 11 '19 at 16:07
  • 4
    It's all about the synchronization context - https://stackoverflow.com/questions/20765723/async-and-await-difference-between-console-windows-forms-and-asp-net – DavidG Mar 11 '19 at 16:08
  • 1
    Yup, you're getting deadlocked because of sync context. Convert to async :) – juunas Mar 11 '19 at 16:18
  • 1
    I exactly hit the same issue and tried similar workarunds before I could find this post and after having blamed myself and many non-culprits in the code. The issue and solution 'sound' weird especially if we aren't well versed with how the synchronisation contxt works. Thanks a bunch for posting the question with answer! – Thimmu Lanka Jul 12 '23 at 05:06

4 Answers4

1

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

mr.coffee
  • 962
  • 8
  • 22
1

Avoid using Result in functions but deal directly with users

User user = await gServiceClient.Users[ID].Request().GetAsync();
Badr Bellaj
  • 11,560
  • 2
  • 43
  • 44
1

I get that this absolutely should not be done under 99.9% of circumstances. But there are times when you dont have a choice. For example in Sitefinity you make widgets via controller classes. 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. 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). It prevents the deadlock (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.

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>();
}
computrius
  • 690
  • 7
  • 16
  • Thanks for this answer. I ran into this issue also on a .net 4.7.2 library that needs to call to graph to get data, and the standard GetAsync().Result pattern would hang forever. Your pattern does work on that application stack. – Kobold_Warlord Apr 07 '23 at 13:17
-2

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

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84