您正在滥用API。
情况就是这样:在ASP.NET中,一次只有一个线程可以处理一个请求。如果有必要,您可以执行一些并行处理(从线程池中借用其他线程),但是只有一个线程具有请求上下文(其他线程没有请求上下文)。
这由ASP.NET管理
SynchronizationContext。
默认情况下,当您使用
awaita时
Task,该方法将在捕获到的对象
SynchronizationContext(或没有捕获
TaskScheduler到的对象
SynchronizationContext)上继续。通常,这就是您想要的:异步控制器操作将执行
await某些操作,并且在恢复操作时,将在请求上下文中恢复操作。
因此,这就是
test5失败的原因:
Test5Controller.Get
执行AsyncAwait_GetSomeDataAsync
(在ASP.NET请求上下文中)。AsyncAwait_GetSomeDataAsync
执行HttpClient.GetAsync
(在ASP.NET请求上下文中)。- HTTP请求被发送出去,并
HttpClient.GetAsync
返回一个uncompletedTask
。 AsyncAwait_GetSomeDataAsync
等待Task
; 由于未完成,因此AsyncAwait_GetSomeDataAsync
返回uncompletedTask
。Test5Controller.Get
阻塞 当前线程,直到Task
完成为止。- HTTP响应进入,
Task
并由返回HttpClient.GetAsync
完成。 AsyncAwait_GetSomeDataAsync
尝试在ASP.NET请求上下文中恢复。但是,在该上下文中已经有一个线程:该线程在中被阻塞Test5Controller.Get
。- 僵局。
这就是其他方法起作用的原因:
- (
test1
,test2
和test3
):在ASP.NET请求上下文 之外 ,Continuations_GetSomeDataAsync
调度到线程池的继续。这使得返回者可以完成而不必重新输入请求上下文。 __Task``Continuations_GetSomeDataAsync
- (
test4
和test6
):由于Task
正在 等待 ,因此不会阻止ASP.NET请求线程。AsyncAwait_GetSomeDataAsync
当准备好继续时,这允许使用ASP.NET请求上下文。
这是最佳做法:
- 在您的“库”
async
方法中,请ConfigureAwait(false)
尽可能使用。在你的情况,这将改变AsyncAwait_GetSomeDataAsync
是var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - 不要阻塞
Task
s;它是async
一路下滑。换句话说,请使用await
代替GetResult
(Task.Result
并且Task.Wait
也应替换为await
)。
这样,您将获得两个好处:延续(
AsyncAwait_GetSomeDataAsync方法的其余部分)在基本线程池线程上运行,该线程不必输入ASP.NET请求上下文;控制器本身是
async(不会阻塞请求线程)。
更多信息:
- 我的
async
/await
介绍性帖子,其中简要介绍了Task
侍者的使用方式SynchronizationContext
。 - 在异步/等待FAQ,其中进入的背景下更详细。另请参见等待,UI和死锁!天啊!即使您使用的是ASP.NET而不是UI,这 也 适用于此,因为ASP.NET一次
SynchronizationContext
将请求上下文限制为仅一个线程。 - 此MSDN论坛帖子。
- Stephen Toub 演示了此死锁(使用UI),Lucian Wischik也进行了演示。
2012年7月13日更新: 将此答案纳入博客文章中。



