簡體   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