我们正在构建一个高度并发的Web应用程序,最近我们已经开始广泛使用异步编程(使用TPL和async
/ await
)。
我们有一个分布式环境,其中应用程序通过REST API(构建在ASP.NET Web API之上)相互通信。在一个特定的应用程序中,我们有一个DelegatingHandler
,在调用base.SendAsync
之后(即,在计算响应之后)将响应记录到文件中。我们在日志中包含响应的基本信息(状态代码,标题和内容):
public static string SerializeResponse(HttpResponseMessage response)
{
var builder = new StringBuilder();
var content = ReadContentAsString(response.Content);
builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode);
builder.AppendLine();
builder.Append(response.Headers);
if (!string.IsNullOrWhiteSpace(content))
{
builder.Append(response.Content.Headers);
builder.AppendLine();
builder.AppendLine(Beautified(content));
}
return builder.ToString();
}
private static string ReadContentAsString(HttpContent content)
{
return content == null ? null : content.ReadAsStringAsync().Result;
}
问题是这样的:当代码在繁重的服务器负载下达到content.ReadAsStringAsync().Result
时,请求有时会挂起在IIS上。如果确实如此,它有时会返回一个响应 - 但在IIS上挂起就像它没有 - 或者在其他时候它永远不会返回。
我还尝试使用ReadAsByteArrayAsync
阅读内容,然后将其转换为String
,但没有运气。
当我将代码转换为使用异步时,我得到更奇怪的结果:
public static async Task<string> SerializeResponseAsync(HttpResponseMessage response)
{
var builder = new StringBuilder();
var content = await ReadContentAsStringAsync(response.Content);
builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode);
builder.AppendLine();
builder.Append(response.Headers);
if (!string.IsNullOrWhiteSpace(content))
{
builder.Append(response.Content.Headers);
builder.AppendLine();
builder.AppendLine(Beautified(content));
}
return builder.ToString();
}
private static Task<string> ReadContentAsStringAsync(HttpContent content)
{
return content == null ? Task.FromResult<string>(null) : content.ReadAsStringAsync();
}
现在HttpContext.Current
在调用content.ReadAsStringAsync()
后为空,而对所有后续请求保持为空!我知道这听起来令人难以置信 - 我花了一些时间和三位同事的存在才接受这种情况真的发生了。
这是某种预期的行为吗?我在这里做错了吗?
答案 0 :(得分:9)
我有这个问题。虽然,我尚未完全测试,但使用CopyToAsync而不是ReadAsStringAsync似乎可以解决问题:
var ms = new MemoryStream();
await response.Content.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin);
var sr = new StreamReader(ms);
responseContent = sr.ReadToEnd();
答案 1 :(得分:1)
关于你的第二个问题,async / await是编译器构建状态机的语法糖,其中对“await”前面的函数的调用立即返回当前线程...包含HttpContext的一个。当前的线程本地存储。该异步调用的完成可以在不同的线程上进行...一个在其线程本地存储中没有HttpContext.Current的线程。
如果您希望完成在同一个线程上执行(因此在线程本地存储中具有相同的对象,如HttpContext.Current),那么您需要了解此行为。这对于来自主UI线程(如果您正在构建Windows应用程序)的调用或在ASP.NET中调用来自ASP.NET请求线程的调用尤其重要,您需要依赖于HttpContext.Current。
请参阅ConfigureAwait上的参考文档(false)。另外,查看有关TPL的一些Channel 9教程。一旦“简单”的东西被弄清楚,主持人就会不可避免地谈论这个问题,因为它会导致一些不容易理解的细微问题,除非你知道TPL在封面下做了什么。
祝你好运。关于您的第一个问题,如果调用者得到结果,我不相信IIS没有完成请求。您是如何确定此调用方启动的ASP.NET请求线程在IIS中挂起的?