编辑: 这个问题看起来可能是相同的问题,但没有任何响应…
编辑: 在测试用例5中,任务似乎停留在WaitingForActivation状态中。
WaitingForActivation
我在.NET 4.5中使用System.Net.Http.HttpClient遇到了一些奇怪的行为-“等待”调用(例如)的结果httpClient.GetAsync(...)将永远不会返回。
httpClient.GetAsync(...)
仅在使用新的异步/等待语言功能和Tasks API的某些情况下会发生这种情况-仅使用延续时,代码似乎总是可以工作。
这是一些重现此问题的代码-将其放入Visual Studio 11中的新“ MVC 4 WebApi项目”中,以暴露以下GET端点:
/api/test1 /api/test2 /api/test3 /api/test4 /api/test5 <--- never completes /api/test6
此处的每个端点都返回相同的数据(来自stackoverflow.com的响应头),但/api/test5永远不会完成。
/api/test5
复制代码:
public class BaseApiController : ApiController { /// <summary> /// Retrieves data using continuations /// </summary> protected Task<string> Continuations_GetSomeDataAsync() { var httpClient = new HttpClient(); var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString()); } /// <summary> /// Retrieves data using async/await /// </summary> protected async Task<string> AsyncAwait_GetSomeDataAsync() { var httpClient = new HttpClient(); var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return result.Content.Headers.ToString(); } } public class Test1Controller : BaseApiController { /// <summary> /// Handles task using Async/Await /// </summary> public async Task<string> Get() { var data = await Continuations_GetSomeDataAsync(); return data; } } public class Test2Controller : BaseApiController { /// <summary> /// Handles task by blocking the thread until the task completes /// </summary> public string Get() { var task = Continuations_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test3Controller : BaseApiController { /// <summary> /// Passes the task back to the controller host /// </summary> public Task<string> Get() { return Continuations_GetSomeDataAsync(); } } public class Test4Controller : BaseApiController { /// <summary> /// Handles task using Async/Await /// </summary> public async Task<string> Get() { var data = await AsyncAwait_GetSomeDataAsync(); return data; } } public class Test5Controller : BaseApiController { /// <summary> /// Handles task by blocking the thread until the task completes /// </summary> public string Get() { var task = AsyncAwait_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test6Controller : BaseApiController { /// <summary> /// Passes the task back to the controller host /// </summary> public Task<string> Get() { return AsyncAwait_GetSomeDataAsync(); } }
您正在滥用API。
情况就是这样:在ASP.NET中,一次只有一个线程可以处理一个请求。如果有必要,您可以执行一些并行处理(从线程池中借用其他线程),但是只有一个线程具有请求上下文(其他线程没有请求上下文)。
这由ASP.NET管理SynchronizationContext。
SynchronizationContext
默认情况下,当您使用awaita时Task,该方法将在捕获到的对象SynchronizationContext(或没有捕获TaskScheduler到的对象SynchronizationContext)上继续。通常,这就是您想要的:异步控制器操作将执行await某些操作,并且在恢复操作时,将在请求上下文中恢复操作。
await
Task
TaskScheduler
因此,这就是test5失败的原因:
test5
Test5Controller.Get
AsyncAwait_GetSomeDataAsync
HttpClient.GetAsync
这就是其他方法起作用的原因:
test1
test2
test3
Continuations_GetSomeDataAsync
Task``Continuations_GetSomeDataAsync
test4
test6
这是最佳做法:
async
ConfigureAwait(false)
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
GetResult
Task.Result
Task.Wait
这样,您将获得两个好处:延续(AsyncAwait_GetSomeDataAsync方法的其余部分)在基本线程池线程上运行,该线程不必输入ASP.NET请求上下文;控制器本身是async(不会阻塞请求线程)。
更多信息:
2012年7月13日更新: 将此答案纳入博客文章中。