[英]Task.Wait() on async method works locally, but not via another method
我最近解決了嘗試執行異步方法時遇到的問題。 對我來說,這提出了比解決還多的問題。 簡而言之,我很高興它能起作用,但我不知道為什么會起作用。
我試圖運行Microsoft Graph Client庫的以下方法:
_graphServiceClient.Users[userPrincipalName].Request().GetAsync()
以下方法不起作用,因為它在使用await
后掛起:
async Task<User> GetUser(string userPrincipalName)
{
User user = await _graphServiceClient.Users[userPrincipalName].Request().GetAsync();
return user;
}
以下方法也不起作用,因為它在執行runTask.Wait()
之后掛起:
User GetUser(string userPrincipalName)
{
return (User) GetResult(_graphServiceClient.Users[userPrincipalName].Request().GetAsync());
}
object GetResult<TResult>(Task<TResult> task)
{
using (task)
using (var runTask = Task.Run(async () => await task))
{
try
{
runTask.Wait();
return runTask.Result;
}
catch (AggregateException e)
{
throw e.InnerException ?? e;
}
}
}
這是奇怪的地方,因為以下代碼確實起作用:
User GetUser(string userPrincipalName)
{
using (var task = Task.Run(async () => await _graphServiceClient.Users[userPrincipalName].Request().GetAsync()))
{
try
{
task.Wait();
return task.Result;
}
catch (AggregateException e)
{
throw e.InnerException ?? e;
}
}
}
雖然我意識到后兩種方法不是最佳實踐,但我對第三種方法為何有效而第二種方法無效卻完全感到困惑。 在我看來,這些幾乎相同。
那么為什么第三種方法行得通,而第二種方法行不通?
首先,您需要了解僵局是如何發生的 。 await
(默認情況下)將捕獲“上下文”,並將在該上下文中繼續執行其余的async
方法 。 在ASP.NET Classic情況下,此“上下文”是請求上下文, 它一次僅允許一個線程運行 。 在線程池線程上運行時,“上下文”是線程池上下文,它將async
方法的其余部分排隊到線程池中,在此線程中任何線程池線程都可以運行它。
阻止異步代碼是一種反模式 。 在具有單線程上下文的情況下(例如ASP.NET Classic),您可能會陷入死鎖。 在第一個死鎖示例中,在ASP.NET Classic上下文中調用了GetUser
,因此它的await
將捕獲該上下文。 然后,調用代碼(也在ASP.NET Classic上下文中運行)將阻止該任務。 這會在該上下文中阻塞線程,從而阻止GetUser
完成,因此最終會導致死鎖。
但是,即使您沒有陷入僵局,也仍然最終會首先放棄異步代碼的所有好處。 考慮“工作”示例,該示例屬於另一個反模式(ASP.NET上的Task.Run
) 。 在這種情況下, Task.Run
導致GetAsync
在線程池上下文中運行,因此在ASP.NET Classic上下文中阻止線程不會死鎖。 但是,這也是一種反模式。
正確的解決方案是始終保持async
狀態 。 具體來說,您的控制器動作應該是async
並在調用異步代碼時使用await
。 只需假裝Wait()
和Result
不存在,您的代碼就會更加快樂。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.