使用await / async时,HttpClient.GetAsync(...)永远不会返回

本文讨论了在使用C# .NET 4.5的HttpClient时遇到的一个问题,即在使用async/await语法时,HttpClient.GetAsync方法永远不会返回。作者通过创建测试用例重现了问题,并探讨了可能的死锁原因。其他用户指出,这可能是由于在ASP.NET环境中不正确地使用异步API导致的,建议避免在异步方法中阻塞任务,并提供了避免死锁的最佳实践。
摘要由CSDN通过智能技术生成

本文翻译自:HttpClient.GetAsync(…) never returns when using await/async

Edit: This question looks like it might be the same problem, but has no responses... 编辑: 这个问题看起来可能是同一个问题,但没有回复......

Edit: In test case 5 the task appears to be stuck in WaitingForActivation state. 编辑:在测试用例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 (eg) 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时才会出现这种情况 - 在仅使用continuation时,代码似乎始终有效。

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/test5 which 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();
    }
}

#1楼

参考:https://stackoom.com/question/hOqm/使用await-async时-HttpClient-GetAsync-永远不会返回


#2楼

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: 和这里:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

And seeing: 并看到:

This type and its members are intended for use by the compiler. 此类型及其成员旨在供编译器使用。

Considering the await version 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


#3楼

You are misusing the API. 您滥用API。

Here's the situation: in ASP.NET, only one thread can handle a request at a time. 情况就是这样:在ASP.NET中,一次只有一个线程可以处理请求。 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). 如有必要,您可以执行一些并行处理(从线程池中借用其他线程),但只有一个线程具有请求上下文(其他线程没有请求上下文)。

This is managed by the ASP.NET SynchronizationContext . 由ASP.NET SynchronizationContext管理

By default, when you await a Task , the method resumes on a captured SynchronizationContext (or a captured TaskScheduler , if there is no SynchronizationContext ). 默认情况下, await Task ,该方法将在捕获的SynchronizationContext (或捕获的TaskScheduler ,如果没有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. 通常,这正是您想要的:异步控制器操作将await某些东西,当它恢复时,它将继续使用请求上下文。

So, here's why test5 fails: 所以,这就是test5失败的原因:

  • Test5Controller.Get executes AsyncAwait_GetSomeDataAsync (within the ASP.NET request context). Test5Controller.Get执行AsyncAwait_GetSomeDataAsync (在ASP.NET请求上下文中)。
  • AsyncAwait_GetSomeDataAsync executes HttpClient.GetAsync (within the ASP.NET request context). AsyncAwait_GetSomeDataAsync执行HttpClient.GetAsync (在ASP.NET请求上下文中)。
  • The HTTP request is sent out, and HttpClient.GetAsync returns an uncompleted Task . HTTP请求被发送出去, HttpClient.GetAsync返回未完成的Task
  • AsyncAwait_GetSomeDataAsync awaits the Task ; AsyncAwait_GetSomeDataAsync等待Task ; since it is not complete, AsyncAwait_GetSomeDataAsync returns an uncompleted Task . 由于它不完整, AsyncAwait_GetSomeDataAsync返回未完成的Task
  • Test5Controller.Get blocks the current thread until that Task completes. Test5Controller.Get 阻塞当前线程,直到该Task完成。
  • The HTTP response comes in, and the Task returned by HttpClient.GetAsync is completed. HTTP响应进入, HttpClient.GetAsync返回的Task完成。
  • AsyncAwait_GetSomeDataAsync attempts to resume within the ASP.NET request context. AsyncAwait_GetSomeDataAsync尝试在ASP.NET请求上下文中恢复。 However, there is already a thread in that context: the thread blocked in Test5Controller.Get . 但是,在该上下文中已经存在一个线程:在Test5Controller.Get阻塞的线程。
  • Deadlock. 僵局。

Here's why the other ones work: 这就是其他人工作的原因:

  • ( test1 , test2 , and test3 ): Continuations_GetSomeDataAsync schedules the continuation to the thread pool, outside the ASP.NET request context. test1test2test3 ): Continuations_GetSomeDataAsync在ASP.NET请求上下文之外调度线程池的延续。 This allows the Task returned by Continuations_GetSomeDataAsync to complete without having to re-enter the request context. 这允许Continuations_GetSomeDataAsync返回的Task完成,而无需重新输入请求上下文。
  • ( test4 and test6 ): Since the Task is awaited , the ASP.NET request thread is not blocked. test4test6 ):由于等待Task ,因此不会阻止ASP.NET请求线程。 This allows AsyncAwait_GetSomeDataAsync to use the ASP.NET request context when it is ready to continue. 这允许AsyncAwait_GetSomeDataAsync在准备好继续时使用ASP.NET请求上下文。

And here's the best practices: 这是最好的做法:

  1. In your "library" async methods, use ConfigureAwait(false) whenever possible. 在“库” async方法中,尽可能使用ConfigureAwait(false) In your case, this would change AsyncAwait_GetSomeDataAsync to be var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); 在您的情况下,这会将AsyncAwait_GetSomeDataAsync更改为var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Don't block on Task s; 不要阻止Task ; it's async all the way down. 它一直是async的。 In other words, use await instead of GetResult ( Task.Result and Task.Wait should also be replaced with await ). 换句话说,使用await代替GetResultTask.ResultTask.Wait也应该替换为await )。

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; 这样,您可以获得两个好处:继续( AsyncAwait_GetSomeDataAsync方法的其余部分)在基本线程池线程上运行,该线程不必进入ASP.NET请求上下文; and the controller itself is async (which doesn't block a request thread). 并且控制器本身是async (它不会阻塞请求线程)。

More information: 更多信息:

Update 2012-07-13: Incorporated this answer into a blog post . 更新2012-07-13:将此答案纳入博客文章


#4楼

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. 现在将在ThreadPool上调用AsyncOperation,其中不存在SynchronizationContext,并且AsyncOperation内部使用的continuation将不会强制返回到调用线程。

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. 确保FooAsync方法中的await没有找到要编组的上下文。 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, eg 最简单的方法是从ThreadPool调用异步工作,例如将调用包装在Task.Run中,例如

int Sync() { return Task.Run(() => Library.FooAsync()).Result; int Sync(){return Task.Run(()=> Library.FooAsync())。结果; } }

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(). 现在将在ThreadPool上调用FooAsync,其中不存在SynchronizationContext,并且FooAsync内部使用的延续不会被强制回到调用Sync()的线程。


#5楼

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. 我有一个在数据库事务属性下的MVC操作。 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. 这不允许上下文切换,否则事务回滚或提交将自行失败。

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. 我只是对每个人说。


#6楼

Since you are using .Result or .Wait or await this will end up causing a deadlock in your code. 由于您使用的是.Result.Waitawait这最终会导致代码死锁

you can use ConfigureAwait(false) in async methods for preventing deadlock 您可以在async方法中使用ConfigureAwait(false)防止死锁

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)来阻止异步代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值