HttpClient.PostAsJsonAsync 永远不会看到 post 何时成功并响应

HttpClient.PostAsJsonAsync never sees when the post is succeeding and responding

我们正在使用 HttpClient post json restful Web 服务。在一个例子中,我们 运行 陷入了让我们感到困惑的事情。使用 postman、fiddler 等工具,我们可以 post 到端点并查看它是否正常工作。当我们对 HttpClient.PostAsJsonAsync 执行相同操作时,我们可以在我们正在 posting 的软件中验证它是否正常接收数据。然而,我们的 PostAsJsonAsync 最终总是会超时,而不是给我们一个响应。

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

每次我们使用 HttpClient 执行 post 时,我们就可以验证我们 post 的目标软件是否确实获取了数据。每当我们从任何其他工具对该目标软件执行 post 时,我们总是很快就会看到状态代码为 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 "";
        }

    }

您有没有遵循异步等待模式的原因?您正在调用异步方法,但没有等待它。您没有说明调用 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 "";
    }

}

这个:

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

导致您的代码死锁。阻塞异步 API 时通常会出现这种情况,这就是为什么您会遇到 "I don't see any response coming back" 效果。

这是怎么导致死锁的?您在包含同步上下文的环境中执行此请求的事实,可能属于 UI。它正在执行异步请求,当响应到达时,它通过一个 IO 完成线程继续,该线程尝试 post 继续到相同的 UI 上下文,该上下文当前被您的 .Result 阻止打电话。

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

这是对@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

System.Net.ServicePointManager.Expect100Continue = false;

我们案例中的一行代码解决了这个问题。来自不同团队的开发人员提供了该建议,并且有效。我还没有 google 它并仔细阅读它以提供对它正在解决的问题的解释。

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

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

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