HttpClient.GetAsync(...)永远不会在使用计谋/异步返回 [英] HttpClient.GetAsync(...) never returns when using await/async

查看:1421
本文介绍了HttpClient.GetAsync(...)永远不会在使用计谋/异步返回的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑: <一个href="http://stackoverflow.com/questions/9895048/async-call-with-await-in-httpclient-never-returns">This问题看起来可能是同样的问题,但一直没有回应......

编辑:在测试用例5任务似乎停留在 WaitingForActivati​​on 状态

我遇到了在.NET 4.5使用System.Net.Http.HttpClient一些奇怪的行为 - 在等待呼叫的结果(例如) httpClient.GetAsync(... )将永远不会返回。

这只能使用新异步发生时,在某些情况下/等待语言功能和任务的API - 在code似乎总是只用延续的时候工作到

下面是一些code抄录问题 - 这拖放到一个新的MVC 4的WebAPI项目在Visual Studio 11,露出下面的GET端点:

  / API / TEST1
/ API / TEST2
/ API / TEST3
/ API / TEST4
/ API / TEST5&LT; ---永远不会完成
/ API / TEST6
 

除了 / API / TEST5 这里的每个端点返回相同的数据(从stackoverflow.com响应头),它永远不会完成。

让我在HttpClient的类中遇到的错误,还是我滥用API以某种方式?

code复制:

 公共类BaseApiController:ApiController
{
    ///&LT;总结&gt;
    ///获取使用数据的延续
    ///&LT; /总结&gt;
    保护任务&LT;字符串&GT; Continuations_GetSomeDataAsync()
    {
        VAR的HttpClient =新的HttpClient();

        VAR T = httpClient.GetAsync(http://stackoverflow.com,HttpCompletionOption.ResponseHeadersRead);

        返回t.ContinueWith(T1 =&GT; t1.Result.Content.Headers.ToString());
    }

    ///&LT;总结&gt;
    ///获取使用异步/计谋数据
    ///&LT; /总结&gt;
    保护异步任务&LT;字符串&GT; AsyncAwait_GetSomeDataAsync()
    {
        VAR的HttpClient =新的HttpClient();

        VAR的结果=等待httpClient.GetAsync(http://stackoverflow.com,HttpCompletionOption.ResponseHeadersRead);

        返回result.Content.Headers.ToString();
    }
}

公共类Test1Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///手柄使用异步/等待任务
    ///&LT; /总结&gt;
    公共异步任务&LT;字符串&GT;得到()
    {
        VAR数据=等待Continuations_GetSomeDataAsync();

        返回的数据;
    }
}

公共类Test2Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///通过阻止线程,直到任务完成手柄任务
    ///&LT; /总结&gt;
    公共字符串获得()
    {
        变种任务= Continuations_GetSomeDataAsync();

        VAR数据= task.GetAwaiter()调用getResult()。

        返回的数据;
    }
}

公共类Test3Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///传递任务回到控制器主机
    ///&LT; /总结&gt;
    公共任务&LT;字符串&GT;得到()
    {
        返回Continuations_GetSomeDataAsync();
    }
}

公共类Test4Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///手柄使用异步/等待任务
    ///&LT; /总结&gt;
    公共异步任务&LT;字符串&GT;得到()
    {
        VAR数据=等待AsyncAwait_GetSomeDataAsync();

        返回的数据;
    }
}

公共类Test5Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///通过阻止线程,直到任务完成手柄任务
    ///&LT; /总结&gt;
    公共字符串获得()
    {
        变种任务= AsyncAwait_GetSomeDataAsync();

        VAR数据= task.GetAwaiter()调用getResult()。

        返回的数据;
    }
}

公共类Test6Controller:BaseApiController
{
    ///&LT;总结&gt;
    ///传递任务回到控制器主机
    ///&LT; /总结&gt;
    公共任务&LT;字符串&GT;得到()
    {
        返回AsyncAwait_GetSomeDataAsync();
    }
}
 

解决方案

正在滥用API。

下面的情况:在ASP.NET中,只有一个线程可以同时处理的请求。你可以做一些并行处理(如有必要,从线程池借用其他线程),但只有一个线程将不得不请求上下文(额外的线程不必请求上下文)。

这是由ASP.NET管理 的SynchronizationContext

在默认情况下,当你计谋 A 工作,该方法继续在捕获 SynchronizationContext的(或捕获的TaskScheduler ,如果没有的SynchronizationContext )。通常情况下,这只是你想要什么:一个异步控制器动作将等待的东西,它恢复时,它与请求上下文恢复

所以,这里的原因 TEST5 失败:

  • Test5Controller.Get 执行 AsyncAwait_GetSomeDataAsync (在ASP.NET请求上下文)。
  • AsyncAwait_GetSomeDataAsync 执行 HttpClient.GetAsync (在ASP.NET请求上下文)。
  • 在HTTP请求发送出去,而 HttpClient.GetAsync 返回一个未完成的工作
  • AsyncAwait_GetSomeDataAsync 等待工作;因为它是不完整的, AsyncAwait_GetSomeDataAsync 返回一个未完成的工作
  • Test5Controller.Get 当前线程,直到工作完成。
  • 在HTTP响应进来,而工作 HttpClient.GetAsync 返回完成。
  • AsyncAwait_GetSomeDataAsync 试图ASP.NET请求范围内恢复。不过,已经有一个线程在这种情况下:受阻于 Test5Controller.Get 线程
  • 僵局。

这也是为什么在其他的工作:

  • 测试1 测试2 TEST3 ) : Continuations_GetSomeDataAsync 调度延续到线程池,之外的ASP.NET请求范围内。这使得工作,而无需重新输入请求上下文由 Continuations_GetSomeDataAsync 完成返回。
  • TEST4 TEST6 ):由于工作期待已久的的,ASP.NET请求线程没有被阻塞。这使得 AsyncAwait_GetSomeDataAsync 使用ASP.NET请求上下文时,它已准备好继续。

而这里的最佳实践:

  1. 在库异步方法,使用 ConfigureAwait(假)只要有可能。在你的情况,这将改变 AsyncAwait_GetSomeDataAsync VAR的结果=等待httpClient.GetAsync(http://stackoverflow.com,HttpCompletionOption.ResponseHeadersRead ).ConfigureAwait(假);
  2. 请不要在工作 S挡;这是异步一路下跌。换句话说,使用等待而不是调用getResult (<$ C C $> Task.Result 和 Task.Wait 也应更换与等待)。

这样的话,你会得到两个好处:延续(在该 AsyncAwait_GetSomeDataAsync 法的其余部分)是运行在一个没有进入一个基本的线程池线程ASP.NET请求上下文;而控制器本身是异步(这不会阻止请求的线程)。

详细信息:

  • 我的<一个href="http://blog.stephencleary.com/2012/02/async-and-await.html"><$c$c>async/<$c$c>await开场后,其中包括工作 awaiters如何使用的SynchronizationContext 的简要说明。
  • 异步/等待FAQ ,其中将详细在上下文。另请参见等待,和用户界面,和死锁!噢,我的!其中的确实的适用于此,即使你在ASP.NET而不是用户界面,因为ASP.NET 的SynchronizationContext 限制了请求上下文只是一个线程在同一时间。
  • 这<一href="http://social.msdn.microsoft.com/Forums/en-AU/async/thread/269172a3-adb9-4b5e-9ac1-8b67ff920177#48b596cc-5eca-4d0e-8743-10d59b64e2ce">MSDN论坛帖子。
  • 斯蒂芬Toub 演示这一僵局(使用UI)和<一href="http://blogs.msdn.com/b/lucian/archive/2012/03/29/talk-async-part-1-the-message-loop-and-the-task-type.aspx">so确实卢西恩Wischik 。

更新2012-07-13:将这一答案的成一​​篇博客文章

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.

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.

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.

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

Each of the endpoints here return the same data (the response headers from stackoverflow.com) except for /api/test5 which never completes.

Have I encountered a bug in the HttpClient class, or am I misusing the API in some way?

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

解决方案

You are misusing the 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).

This is managed by the 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). Normally, this is just what you want: an asynchronous controller action will await something, and when it resumes, it resumes with the request context.

So, here's why test5 fails:

  • Test5Controller.Get executes AsyncAwait_GetSomeDataAsync (within the ASP.NET request context).
  • AsyncAwait_GetSomeDataAsync executes HttpClient.GetAsync (within the ASP.NET request context).
  • The HTTP request is sent out, and HttpClient.GetAsync returns an uncompleted Task.
  • AsyncAwait_GetSomeDataAsync awaits the Task; since it is not complete, AsyncAwait_GetSomeDataAsync returns an uncompleted Task.
  • Test5Controller.Get blocks the current thread until that Task completes.
  • The HTTP response comes in, and the Task returned by HttpClient.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 in 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. This allows the Task returned by Continuations_GetSomeDataAsync to complete without having to re-enter the request context.
  • (test4 and test6): Since the Task is awaited, the ASP.NET request thread is not blocked. This allows AsyncAwait_GetSomeDataAsync to use the ASP.NET request context when it is ready to continue.

And here's the best practices:

  1. In your "library" async methods, use ConfigureAwait(false) whenever possible. In your case, this would change AsyncAwait_GetSomeDataAsync to be var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Don't block on Tasks; it's async all the way down. In other words, use await instead of GetResult (Task.Result and Task.Wait should also be replaced with 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; and the controller itself is async (which doesn't block a request thread).

More information:

Update 2012-07-13: Incorporated this answer into a blog post.

这篇关于HttpClient.GetAsync(...)永远不会在使用计谋/异步返回的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆