HttpClient.PostAsJsonAsync永远看不到帖子何时成功并做出响应

本文关键字:成功 响应 何时 PostAsJsonAsync 永远 看不到 HttpClient | 更新日期: 2023-09-27 17:53:21

我们使用HttpClient将json发布到restful web服务。在一个例子中,我们遇到了一些让我们困惑的事情。使用poster、fiddler等工具,我们可以发布到一个端点,并查看它是否正常工作。当我们对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 "";
        }
    }

HttpClient.PostAsJsonAsync永远看不到帖子何时成功并做出响应

这:

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

导致代码死锁。在异步API上阻塞时通常会出现这种情况,这就是为什么您会遇到"我看不到任何响应">效果。

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

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

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

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

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

是否有原因不遵循异步等待模式?您正在调用一个异步方法,但没有等待它。您没有说明调用REST服务的代码是Windows窗体还是ASP.NET应用程序,但.Result可能会导致您出现问题。

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

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 "";
    }
}

这是对@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模式将该方法重命名为PostDataAsync

System.Net.ServicePointManager.Expect100Continue = false;

在我们的案例中,这一行代码解决了问题。来自另一个团队的开发人员提出了这个建议,而且效果很好。我还没有在谷歌上搜索并阅读足够多的内容来解释它所解决的问题。