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

查看:72
本文介绍了HttpClient.GetAsync(...) 在使用 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 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 将延续调度到线程池,外部 ASP.NET 请求上下文.这允许 Continuations_GetSomeDataAsync 返回的 Task 无需重新输入请求上下文即可完成.
  • (test4 and test6):由于Taskawaited,ASP.NET请求线程是没有被阻止.这允许 AsyncAwait_GetSomeDataAsync 在准备好继续时使用 ASP.NET 请求上下文.
  • (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.

以下是最佳做法:

  1. 在您的库"async 方法中,尽可能使用 ConfigureAwait(false).在您的情况下,这会将 AsyncAwait_GetSomeDataAsync 更改为 var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. 不要阻塞Task;它一直是async.换句话说,使用 await 而不是 GetResult(Task.ResultTask.Wait 也应该替换为等待).
  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).

这样,您将获得两个好处:延续(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).

更多信息:

  • My async/await intro post, which includes a brief description of how Task awaiters use SynchronizationContext.
  • 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屋!

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