HttpClient.GetAsync(...) 在使用 await/async 时永远不会返回 [英] HttpClient.GetAsync(...) never returns when using await/async
问题描述
这个问题 看起来可能是同样的问题,但没有回应...
This question looks like it might be the same problem, but has no responses...
在测试用例 5 中,任务似乎停留在 WaitingForActivation
状态.
In test case 5 the task appears to be stuck in WaitingForActivation
state.
我在 .NET 4.5 中使用 System.Net.Http.HttpClient 遇到了一些奇怪的行为 - 其中等待"调用(例如)httpClient.GetAsync(...)代码>永远不会返回.
I've encountered some odd behaviour using the System.Net.Http.HttpClient in .NET 4.5 - where "awaiting" the result of a call to (e.g.) httpClient.GetAsync(...)
will never return.
这仅在使用新的 async/await 语言功能和 Tasks API 的特定情况下发生 - 代码在仅使用延续时似乎总是有效.
This only occurs in certain circumstances when using the new async/await language functionality and Tasks API - the code always seems to work when using only continuations.
这是一些重现问题的代码 - 将其放入 Visual Studio 11 中的新MVC 4 WebApi 项目"中以公开以下 GET 端点:
Here's some code which reproduces the problem - drop this into a new "MVC 4 WebApi project" in Visual Studio 11 to expose the following GET endpoints:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
此处的每个端点都返回相同的数据(来自 stackoverflow.com 的响应标头),除了永远不会完成的 /api/test5
.
Each of the endpoints here return the same data (the response headers from stackoverflow.com) except for /api/test5
which never completes.
要重现的代码:
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 中,一次只有一个线程可以处理一个请求.如果需要,您可以进行一些并行处理(从线程池中借用额外的线程),但只有一个线程会有请求上下文(额外的线程没有请求上下文).
Here's the situation: in ASP.NET, only one thread can handle a request at a time. You can do some parallel processing if necessary (borrowing additional threads from the thread pool), but only one thread would have the request context (the additional threads do not have the request context).
这是 由 ASP.NET SynchronizationContext
管理.
This is managed by the ASP.NET SynchronizationContext
.
默认情况下,当您 await
一个 Task
时,该方法会在捕获的 SynchronizationContext
(或捕获的 TaskScheduler
)上恢复code>,如果没有 SynchronizationContext
).通常,这正是您想要的:异步控制器操作将await
某些东西,当它恢复时,它会以请求上下文恢复.
By default, when you await
a Task
, the method resumes on a captured SynchronizationContext
(or a captured TaskScheduler
, if there is no SynchronizationContext
). Normally, this is just what you want: an asynchronous controller action will await
something, and when it resumes, it resumes with the request context.
那么,这就是 test5
失败的原因:
So, here's why test5
fails:
Test5Controller.Get
执行AsyncAwait_GetSomeDataAsync
(在 ASP.NET 请求上下文中).AsyncAwait_GetSomeDataAsync
执行HttpClient.GetAsync
(在 ASP.NET 请求上下文中).- HTTP 请求发出,
HttpClient.GetAsync
返回一个未完成的Task
. AsyncAwait_GetSomeDataAsync
等待Task
;由于未完成,AsyncAwait_GetSomeDataAsync
返回未完成的Task
.Test5Controller.Get
阻塞当前线程,直到Task
完成.- HTTP响应进来,
HttpClient.GetAsync
返回的Task
就完成了. AsyncAwait_GetSomeDataAsync
尝试在 ASP.NET 请求上下文中恢复.但是,该上下文中已经有一个线程:在Test5Controller.Get
中阻塞的线程.- 僵局.
Test5Controller.Get
executesAsyncAwait_GetSomeDataAsync
(within the ASP.NET request context).AsyncAwait_GetSomeDataAsync
executesHttpClient.GetAsync
(within the ASP.NET request context).- The HTTP request is sent out, and
HttpClient.GetAsync
returns an uncompletedTask
. AsyncAwait_GetSomeDataAsync
awaits theTask
; since it is not complete,AsyncAwait_GetSomeDataAsync
returns an uncompletedTask
.Test5Controller.Get
blocks the current thread until thatTask
completes.- The HTTP response comes in, and the
Task
returned byHttpClient.GetAsync
is completed. AsyncAwait_GetSomeDataAsync
attempts to resume within the ASP.NET request context. However, there is already a thread in that context: the thread blocked inTest5Controller.Get
.- Deadlock.
这就是为什么其他的工作:
Here's why the other ones work:
- (
test1
,test2
, andtest3
):Continuations_GetSomeDataAsync
将延续调度到线程池,外部 ASP.NET 请求上下文.这允许Continuations_GetSomeDataAsync
返回的Task
无需重新输入请求上下文即可完成. - (
test4
andtest6
):由于Task
是awaited,ASP.NET请求线程是没有被阻止.这允许AsyncAwait_GetSomeDataAsync
在准备好继续时使用 ASP.NET 请求上下文.
- (
test1
,test2
, andtest3
):Continuations_GetSomeDataAsync
schedules the continuation to the thread pool, outside the ASP.NET request context. This allows theTask
returned byContinuations_GetSomeDataAsync
to complete without having to re-enter the request context. - (
test4
andtest6
): Since theTask
is awaited, the ASP.NET request thread is not blocked. This allowsAsyncAwait_GetSomeDataAsync
to use the ASP.NET request context when it is ready to continue.
以下是最佳做法:
- 在您的库"
async
方法中,尽可能使用ConfigureAwait(false)
.在您的情况下,这会将AsyncAwait_GetSomeDataAsync
更改为var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);代码>
- 不要阻塞
Task
;它一直是async
.换句话说,使用await
而不是GetResult
(Task.Result
和Task.Wait
也应该替换为等待
).
- In your "library"
async
methods, useConfigureAwait(false)
whenever possible. In your case, this would changeAsyncAwait_GetSomeDataAsync
to bevar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- Don't block on
Task
s; it'sasync
all the way down. In other words, useawait
instead ofGetResult
(Task.Result
andTask.Wait
should also be replaced withawait
).
这样,您将获得两个好处:延续(AsyncAwait_GetSomeDataAsync
方法的其余部分)在基本线程池线程上运行,该线程池线程不必进入 ASP.NET 请求上下文;并且控制器本身是async
(它不会阻塞请求线程).
That way, you get both benefits: the continuation (the remainder of the AsyncAwait_GetSomeDataAsync
method) is run on a basic thread pool thread that doesn't have to enter the ASP.NET request context; and the controller itself is async
(which doesn't block a request thread).
更多信息:
- 我的
async
/await
介绍帖子,其中包括对Task
等待者如何使用SynchronizationContext
的简要说明. - 异步/等待常见问题解答,其中更详细地介绍了上下文.另请参阅等待、UI 和死锁!哦,天哪!确实适用于这里,即使您使用的是 ASP.NET 而不是 UI,因为 ASP.NET
SynchronizationContext
限制了请求上下文一次只有一个线程. - 这个MSDN 论坛帖子.
- Stephen Toub 演示这个僵局(使用 UI),以及所以Lucian Wischik 是吗.
- My
async
/await
intro post, which includes a brief description of howTask
awaiters useSynchronizationContext
. - The Async/Await FAQ, which goes into more detail on the contexts. Also see Await, and UI, and deadlocks! Oh, my! which does apply here even though you're in ASP.NET rather than a UI, because the ASP.NET
SynchronizationContext
restricts the request context to just one thread at a time. - This MSDN forum post.
- Stephen Toub demos this deadlock (using a UI), and so does Lucian Wischik.
2012 年 7 月 13 日更新: 合并了此答案 进入博文.
这篇关于HttpClient.GetAsync(...) 在使用 await/async 时永远不会返回的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!