[英]Microsoft.Graph GetAsync() hangs indefinitely
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;
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.我意识到这可能是一个小众问题,但我确实希望有人遇到过同样的问题并能够解决它。
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.
看起来我的伪代码并不完全准确,解决方案就在其中。
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());
}
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;
}
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?
谁能解释这是为什么?
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:它的:
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.