C# HttpClient.GetAsync(...) 在使用 await/async 时永远不会返回
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10343632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HttpClient.GetAsync(...) never returns when using await/async
提问by Benjamin Fox
Edit:This questionlooks like it might be the same problem, but has no responses...
编辑:这个问题看起来可能是同样的问题,但没有回应......
Edit:In test case 5 the task appears to be stuck in WaitingForActivationstate.
编辑:在测试用例 5 中,任务似乎处于停滞WaitingForActivation状态。
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.
我在 .NET 4.5 中使用 System.Net.Http.HttpClient 遇到了一些奇怪的行为 - 其中“等待”调用(例如)的结果httpClient.GetAsync(...)将永远不会返回。
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.
这仅在使用新的 async/await 语言功能和 Tasks API 时发生在某些情况下 - 代码似乎总是在仅使用延续时工作。
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:
这是一些重现问题的代码 - 将其放入 Visual Studio 11 中的新“MVC 4 WebApi 项目”中以公开以下 GET 端点:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Each of the endpoints here return the same data (the response headers from stackoverflow.com) except for /api/test5which never completes.
这里的每个端点都返回相同的数据(来自 stackoverflow.com 的响应标头),除了/api/test5它们永远不会完成。
Have I encountered a bug in the HttpClient class, or am I misusing the API in some way?
我是否在 HttpClient 类中遇到了错误,或者我是否以某种方式滥用了 API?
Code to reproduce:
重现代码:
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();
}
}
采纳答案by Stephen Cleary
You are misusing the API.
您正在滥用 API。
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 中,一次只有一个线程可以处理一个请求。如有必要,您可以进行一些并行处理(从线程池中借用附加线程),但只有一个线程具有请求上下文(附加线程没有请求上下文)。
This is managed by the ASP.NET SynchronizationContext.
这是由 ASP.NET 管理的SynchronizationContext。
By default, when you awaita 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 awaitsomething, and when it resumes, it resumes with the request context.
默认情况下,当您awaita 时Task,该方法在捕获的SynchronizationContext(或捕获的TaskScheduler,如果没有SynchronizationContext)上恢复。通常,这正是您想要的:异步控制器操作会执行await某些操作,当它恢复时,它会在请求上下文中恢复。
So, here's why test5fails:
所以,这就是test5失败的原因:
Test5Controller.GetexecutesAsyncAwait_GetSomeDataAsync(within the ASP.NET request context).AsyncAwait_GetSomeDataAsyncexecutesHttpClient.GetAsync(within the ASP.NET request context).- The HTTP request is sent out, and
HttpClient.GetAsyncreturns an uncompletedTask. AsyncAwait_GetSomeDataAsyncawaits theTask; since it is not complete,AsyncAwait_GetSomeDataAsyncreturns an uncompletedTask.Test5Controller.Getblocksthe current thread until thatTaskcompletes.- The HTTP response comes in, and the
Taskreturned byHttpClient.GetAsyncis completed. AsyncAwait_GetSomeDataAsyncattempts to resume within the ASP.NET request context. However, there is already a thread in that context: the thread blocked inTest5Controller.Get.- Deadlock.
Test5Controller.Get执行AsyncAwait_GetSomeDataAsync(在 ASP.NET 请求上下文中)。AsyncAwait_GetSomeDataAsync执行HttpClient.GetAsync(在 ASP.NET 请求上下文中)。- HTTP 请求被发送出去,并
HttpClient.GetAsync返回一个未完成的Task. AsyncAwait_GetSomeDataAsync等待着Task; 因为它不完整,所以AsyncAwait_GetSomeDataAsync返回一个 uncompletedTask。Test5Controller.Get阻塞当前线程直到Task完成。- HTTP响应进来,
Task返回HttpClient.GetAsync完成。 AsyncAwait_GetSomeDataAsync尝试在 ASP.NET 请求上下文中恢复。但是,该上下文中已经有一个线程:该线程在Test5Controller.Get.- 僵局。
Here's why the other ones work:
这就是其他人工作的原因:
- (
test1,test2, andtest3):Continuations_GetSomeDataAsyncschedules the continuation to the thread pool, outsidethe ASP.NET request context. This allows theTaskreturned byContinuations_GetSomeDataAsyncto complete without having to re-enter the request context. - (
test4andtest6): Since theTaskis awaited, the ASP.NET request thread is not blocked. This allowsAsyncAwait_GetSomeDataAsyncto use the ASP.NET request context when it is ready to continue.
- (
test1、test2、 和test3):在 ASP.NET 请求上下文之外Continuations_GetSomeDataAsync调度到线程池的延续。这允许返回的完成而无需重新输入请求上下文。TaskContinuations_GetSomeDataAsync - (
test4andtest6): 由于Task是awaited,ASP.NET 请求线程不会被阻塞。这允许AsyncAwait_GetSomeDataAsync在准备好继续时使用 ASP.NET 请求上下文。
And here's the best practices:
以下是最佳实践:
- In your "library"
asyncmethods, useConfigureAwait(false)whenever possible. In your case, this would changeAsyncAwait_GetSomeDataAsyncto bevar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - Don't block on
Tasks; it'sasyncall the way down. In other words, useawaitinstead ofGetResult(Task.ResultandTask.Waitshould also be replaced withawait).
- 在您的“库”
async方法中,ConfigureAwait(false)尽可能使用。在你的情况,这将改变AsyncAwait_GetSomeDataAsync是var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - 不要在
Tasks 上阻塞;它async一直在下降。换句话说,使用await代替GetResult(Task.Result并且Task.Wait也应该替换为await)。
That way, you get both benefits: the continuation (the remainder of the AsyncAwait_GetSomeDataAsyncmethod) 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).
这样,您将获得两个好处:继续(AsyncAwait_GetSomeDataAsync方法的其余部分)在一个基本线程池线程上运行,该线程池线程不必进入 ASP.NET 请求上下文;并且控制器本身是async(它不会阻止请求线程)。
More information:
更多信息:
- My
async/awaitintro post, which includes a brief description of howTaskawaiters useSynchronizationContext. - The Async/Await FAQ, which goes into more detail on the contexts. Also see Await, and UI, and deadlocks! Oh, my!which doesapply here even though you're in ASP.NET rather than a UI, because the ASP.NET
SynchronizationContextrestricts 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.
- 我的
async/awaitintro 帖子,其中包括对Task等待者如何使用SynchronizationContext. - 在异步/等待FAQ,其中进入的背景下更详细。另请参阅等待、UI 和死锁!天啊!即使您在 ASP.NET 而不是 UI 中,这也适用于此处,因为 ASP.NET
SynchronizationContext将请求上下文限制为一次只有一个线程。 - 这个MSDN 论坛帖子。
- Stephen Toub演示了这个僵局(使用 UI),Lucian Wischik也是如此。
Update 2012-07-13:Incorporated this answer into a blog post.
2012 年 7 月 13 日更新:将此答案合并到博客文章中。
回答by yamen
I'm looking here:
我在这里看:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
And here:
和这里:
And seeing:
并看到:
This type and its members are intended for use by the compiler.
此类型及其成员旨在供编译器使用。
Considering the awaitversion works, and is the 'right' way of doing things, do you really need an answer to this question?
考虑到await版本有效,并且是“正确”的做事方式,您真的需要回答这个问题吗?
My vote is: Misusing the API.
我的投票是:滥用 API。
回答by Ykok
Edit: Generally try to avoid doing the below except as a last ditch effort to avoid deadlocks. Read the first comment from Stephen Cleary.
编辑:通常尽量避免执行以下操作,除非作为避免死锁的最后努力。阅读 Stephen Cleary 的第一条评论。
Quick fix from here. Instead of writing:
从这里快速修复。而不是写:
Task tsk = AsyncOperation();
tsk.Wait();
Try:
尝试:
Task.Run(() => AsyncOperation()).Wait();
Or if you need a result:
或者,如果您需要结果:
var result = Task.Run(() => AsyncOperation()).Result;
From the source (edited to match the above example):
从源(编辑以匹配上面的例子):
AsyncOperation will now be invoked on the ThreadPool, where there won't be a SynchronizationContext, and the continuations used inside of AsyncOperation won't be forced back to the invoking thread.
AsyncOperation 现在将在没有 SynchronizationContext 的 ThreadPool 上调用,并且 AsyncOperation 内部使用的延续不会被强制返回到调用线程。
For me this looks like a useable option since I do not have the option of making it async all the way (which I would prefer).
对我来说,这看起来像是一个可用的选项,因为我没有办法让它一直异步(我更喜欢)。
From the source:
从来源:
Ensure that the await in the FooAsync method doesn't find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g.
int Sync() { return Task.Run(() => Library.FooAsync()).Result; }
FooAsync will now be invoked on the ThreadPool, where there won't be a SynchronizationContext, and the continuations used inside of FooAsync won't be forced back to the thread that's invoking Sync().
确保 FooAsync 方法中的 await 找不到要封送回的上下文。最简单的方法是从 ThreadPool 调用异步工作,例如通过将调用包装在 Task.Run 中,例如
int Sync() { return Task.Run(() => Library.FooAsync()).Result; }
现在将在 ThreadPool 上调用 FooAsync,其中不会有 SynchronizationContext,并且在 FooAsync 内部使用的延续不会被强制返回到调用 Sync() 的线程。
回答by alex.peter
These two schools are not really excluding.
这两个学校并不是真的排斥。
Here is the scenario where you simply have to use
这是您只需要使用的场景
Task.Run(() => AsyncOperation()).Wait();
or something like
或类似的东西
AsyncContext.Run(AsyncOperation);
I have a MVC action that is under database transaction attribute. The idea was (probably) to roll back everything done in the action if something goes wrong. This does not allow context switching, otherwise transaction rollback or commit is going to fail itself.
我有一个位于数据库事务属性下的 MVC 操作。这个想法(可能)是在出现问题时回滚操作中所做的一切。这不允许上下文切换,否则事务回滚或提交本身就会失败。
The library I need is async as it is expected to run async.
我需要的库是异步的,因为它应该运行异步。
The only option. Run it as a normal sync call.
唯一的选择。将其作为普通同步调用运行。
I am just saying to each its own.
我只是对每个人说自己的。
回答by Hasan Fathi
Since you are using .Resultor .Waitor awaitthis will end up causing a deadlockin your code.
由于您正在使用.Resultor.Wait或await这最终会导致您的代码出现死锁。
you can use ConfigureAwait(false)in asyncmethods for preventing deadlock
您可以ConfigureAwait(false)在防止死锁的async方法中使用
like this:
像这样:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
you can use
ConfigureAwait(false)wherever possible for Don't Block Async Code .
您可以
ConfigureAwait(false)尽可能使用 Don't Block Async Code 。
回答by Bondolin
I'm going to put this in here more for completeness than direct relevance to the OP. I spent nearly a day debugging an HttpClientrequest, wondering why I was never getting back a response.
我将把它放在这里更多的是为了完整性,而不是与 OP 直接相关。我花了将近一天的时间调试一个HttpClient请求,想知道为什么我从来没有得到回复。
Finally found that I had forgotten to awaitthe asynccall further down the call stack.
最后发现,我忘了await该async电话进一步下跌的调用堆栈。
Feels about as good as missing a semicolon.
感觉就像缺少一个分号一样好。

