我最近解决了尝试执行异步方法时遇到的问题。对我来说,这提出了比解决还多的问题。简而言之,我很高兴它能起作用,但是我不知道为什么会起作用。
我正在尝试运行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;
}
}
}
虽然我意识到后两种方法不被视为最佳实践,但我完全困惑第三种方法为何有效而第二种无效的原因。在我看来,这些几乎相同。
那为什么第三个方法有效,而第二个方法却无效?
答案 0 :(得分:4)
首先,您需要了解how the deadlock happens。 await
(默认情况下)将捕获“上下文”,并将resume executing the rest of the async
method in that context。在ASP.NET Classic情况下,此“上下文”是请求上下文,仅允许一次运行一个线程。在线程池线程上运行时,“上下文”是线程池上下文,它仅将async
方法的其余部分排队到线程池中,该线程可以在任何线程池线程中运行。
Blocking on asynchronous code is an antipattern。在具有单线程上下文的情况下(例如ASP.NET Classic),您可能会陷入死锁。在第一个死锁示例中,在ASP.NET Classic上下文中调用了GetUser
,因此其await
将捕获该上下文。然后,调用代码(也在ASP.NET Classic上下文中运行)将阻止该任务。这样会在该上下文中阻塞线程,从而阻止GetUser
完成,因此最终导致死锁。
但是,即使您没有陷入僵局,您仍然仍然会首先放弃异步代码的所有好处。考虑“工作”示例,该示例属于另一个antipattern (Task.Run
on ASP.NET)。在这种情况下,Task.Run
导致GetAsync
在线程池上下文中运行,因此在ASP.NET Classic上下文中阻止线程不会死锁。但是,这也是一种反模式。
正确的解决方案是async
all the way。具体来说,您的控制器操作应为async
,并在调用异步代码时使用await
。只需假装Wait()
和Result
不存在,您的代码就会更加快乐。