【问题标题】:HttpClient.PostAsJsonAsync never sees when the post is succeeding and respondingHttpClient.PostAsJsonAsync 永远不会看到帖子何时成功并响应
【发布时间】:2015-06-04 20:42:33
【问题描述】:

我们正在使用 HttpClient 将 json 发布到一个安静的 Web 服务。在一个例子中,我们遇到了让我们感到困惑的事情。使用邮递员、提琴手等工具,我们可以发布到端点并查看它是否正常工作。当我们对 HttpClient.PostAsJsonAsync 执行相同操作时,我们可以在我们发布到的软件中验证它是否正常接收了数据。然而,我们的 PostAsJsonAsync 最终总会超时而不是给我们响应。

我们已经与创建我们正在使用的服务的团队合作,加上我们这边的额外测试,我们还不能真正让该服务超时。

每次我们使用 HttpClient 进行发布时,我们都可以验证我们发布到的目标软件是否确实获取了数据。每当我们从任何其他工具向该目标软件发布帖子时,我们总是很快就会看到状态码为 200 的响应。关于 HttpClient 的某些东西无法接受来自此特定服务的响应。有谁知道我们可以从这里看到什么?

这是代码(虽然它太俗套了,我几乎觉得没有必要)

public string PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
    {
        using (var client = new HttpClient())
        {
            if (timeoutMinutes > 0)
            {
                client.Timeout = new TimeSpan(0,timeoutMinutes,0);
            }
            var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
            var response = client.PostAsJsonAsync(useUrl, o).Result;
            if(response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                return response.Content.ReadAsStringAsync().Result;
            }
            return "";
        }

    }

【问题讨论】:

  • 我实际上在使用 WebClient 时遇到了同样的问题。发布的数据反映在我们发布到的目标系统中,但我们的请求挂起。我开始认为这与自签名证书有关。我们正在 https 上连接到此服务的阶段/测试实例,并且证书可能没有有效的签名。然而,到目前为止,我似乎无法弄清楚如何验证这是否真的是问题。

标签: c# async-await dotnet-httpclient connection-timeout


【解决方案1】:

这个:

var response = client.PostAsJsonAsync(useUrl, o).Result;

导致您的代码死锁。阻塞异步 API 时经常出现这种情况,这就是为什么您会遇到 “我没有看到任何响应返回” 效果。

这是如何导致死锁的?您在包含同步上下文的环境中执行此请求这一事实,可能属于 UI。它正在执行异步请求,当响应到达时,它会通过一个 IO 完成线程继续,该线程尝试将延续发布到同一 UI 上下文中,该上下文当前被您的 .Result 调用阻止。

如果要同步发出 HTTP 请求,请改用 WebClient。如果你想正确利用异步 API,那么await 而不是用.Result 阻止。

【讨论】:

  • 感谢您的回答。当我第一次查看 Web 客户端与 http 客户端时,我读到 Http 客户端更新,并且给人的印象是 Web 客户端将被弃用的未来。我还看到一些东西说像我的代码那样执行 .Result 是一种可接受的以阻塞方式使用 HttpClient 的方式。我将再看看网络客户端。谢谢。
  • @Danny 有时人们并不真正理解他们误用了异步 API。您还可以使用 HttpWebRequest 去“原始”。
  • Webclient 也在做同样的事情。我认为这可能是 .net 代码不喜欢我们正在使用的服务的测试/暂存实例上的自签名证书。花时间研究如何消除这种可能性,或者如果这是问题就解决它。
  • @Danny 即使是同步方法调用也会导致冻结?你试过摆弄请求吗?如果您删除 SSC 并使用 Http,会发生这种情况吗?
  • 是的,我没有意识到我们正在通过代理(mulesoft API 管理器)访问服务。如果我们直接访问该服务,我们可以让 webclient 工作。当我们反对 api manager 代理时,我们被卡住了。所以我们正在缩小我们的问题范围。
【解决方案2】:

我遇到了同样的问题,this SO answer 为我解决了这个问题。

简而言之,您必须使用ConfigureAwait(false) 扩展名来避免死锁:

var response = await client.PostAsJsonAsync(useUrl, o).ConfigureAwait(false);

【讨论】:

  • 请注意,如果您走这条路线以避免 await 捕获您的 UI 上下文,您需要在您的 .Result 调用之前/下方在 everything 上设置 ConfigureAwait(false)。因此,它被认为有点脆弱。
【解决方案3】:

您不遵循异步等待模式是否有原因?您正在调用异步方法,但不等待它。您没有说调用 REST 服务的代码是 Windows 窗体还是 ASP.NET 应用程序,但 .Resultprobably causing you issues

你能像这样重组你的方法吗:

public async Task<string> PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
    using (var client = new HttpClient())
    {
        if (timeoutMinutes > 0)
        {
            client.Timeout = new TimeSpan(0,timeoutMinutes,0);
        }
        var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
        var response = await client.PostAsJsonAsync(useUrl, o);
        if(response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return await response.Content.ReadAsStringAsync();
        }
        return "";
    }

}

【讨论】:

  • 感谢您抽出宝贵时间回复。就我而言,我确实希望它是一个同步调用。在 Web api 服务器上调用此代码时可以正常工作。我们有第二个环境,代码在我们所谓的“任务服务器”上运行,它本身就是一个在服务器上运行的服务,它需要一个排队的任务列表并在其线程池中运行多个任务。在那种环境下,我们没有得到回应。所以我怀疑是环境的不同。
  • 我可能会使用 Web 客户端,因为我确实希望它能够同步运行。我读过 HttpClient 最终会弃用 Web 客户端,而且它对于同步的东西仍然很有效。我猜我看错了:)
  • @Danny - 在引入 HttpClient 之前,我是 RestSharp 的粉丝。这也可能是另一种选择。
【解决方案4】:

这是对@Justin Helgerson 解决方案的轻微修改。您的方法中有 2 个阻塞 .Result 调用;一旦你去异步,你应该修复它们。

public async Task<string> PostDataAsync(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
    using (var client = new HttpClient())
    {
        if (timeoutMinutes > 0)
        {
            client.Timeout = new TimeSpan(0,timeoutMinutes,0);
        }
        var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
        var response = await client.PostAsJsonAsync(useUrl, o);
        if(response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return await response.Content.ReadAsStringAsync();
        }
        return "";
    }
}

注意,我还根据TAP pattern 将方法重命名为PostDataAsync

【讨论】:

  • 很好,我没有看到另一个.Result 电话。
【解决方案5】:
System.Net.ServicePointManager.Expect100Continue = false;

在我们的例子中,一行代码解决了这个问题。来自不同团队的开发人员提出了该建议,并且有效。我还没有用谷歌搜索它并阅读它足以解释它正在解决的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多