响应.当我试图同时读取请求和写入响应时,写入挂起

本文关键字:响应 挂起 求和 请求 读取 | 更新日期: 2023-09-27 18:10:54

问题

这几天来一直让我发疯。我们在这里有一个过程,通过ASP.NET Web Forms(.NET 4.0(项目中的管理页面,将记录从csv文件导入数据库。这个过程本身太慢了,我有责任让它更快。我首先更改了核心逻辑,这大大提高了性能。

但是,如果我上传大文件(好吧,相对较大,最多约3 MB(,我必须等到上传过程结束,直到我开始导入,并且在导入过程中我不会向客户端返回任何进度。过程本身没有那么长,大约需要5到10秒才能完成,是的,我已经考虑创建一个单独的Task并轮询服务器,但我觉得这太过分了。


到目前为止我做了什么

因此,为了解决这个问题,我决定在读取时读取传入流并导入值。我创建了一个通用处理程序(.ashx(,并将以下代码放入void ProcessRequest(HttpContext context):中

using (var stream = context.Request.GetBufferlessInputStream())
{
}

首先,我删除标题,然后读取流(通过StreamReader(,直到得到CRLF,将行转换为我的模型对象,并继续读取csv。当我得到大约200条记录时,我会将所有记录批量更新到数据库中。然后,我不断得到更多的记录,直到我结束文件或得到200条记录。

这似乎奏效了,但后来,我决定也流式传输我的回应。首先,我禁用了BufferOutput:

context.Response.BufferOutput = false;

然后,我在回复中添加了这些标题:

context.Response.AddHeader("Keep-Alive", "true");
context.Response.AddHeader("Cache-Control", "no-cache");
context.Response.ContentType = "application/X-MyUpdate";

然后,在将这200条记录发送到数据库后,我写了一个响应:

response.Write(s);
response.Flush();

s是一个固定大小为256个字符的字符串。我知道256个字符并不总是等于256个字节,但我只是确信我不会在响应中写下文字墙,把事情搞砸。

这是它的格式:

| pipeline (record delimiter)
1 or 0 success or failure
; delimiter
error message (if applicable)
| pipeline (next demiliter and so on)

示例:

|0;"Invalid price on line 123"|1;|1;|0;"Invalid id on line 127"|

在客户端,以下是我所拥有的(只是请求部分(:

function import(){
    var formData = new FormData();
    var file = $('[data-id=file]')[0].files[0];
    formData.append('file', file);
    var xhr = new XMLHttpRequest();
    var url = "/Site/Update.ashx";
    xhr.onprogress = updateProgress;
    xhr.open('POST', url, true);
    xhr.setRequestHeader("Content-Type", "multipart/form-data");
    xhr.setRequestHeader("X-File-Name", file.name);
    xhr.setRequestHeader("X-File-Type", file.type);
    xhr.send(formData);
}
function updateProgress(evt){
    debugger;
}

发生了什么:(

  • 当我调用response.Flush时,它不会立即将数据发送到客户端。我知道客户端有缓冲,但它似乎根本不起作用,即使我发送了很多伪数据来绕过这个问题
  • 一段时间后,当我在Response.Write上写太多东西时,该方法会变得越来越慢,直到挂起。与Response.Flush相同。我想我错过了什么
  • 我创建了一个简单的webforms项目来测试我一直在尝试做的事情。它有一个通用的处理程序,它将在10秒内每秒返回一个数字。它实际上是在更新(并不总是以1秒的方式(,我可以看到正在进行的进展
  • 当我只写几行回复时,它实际上显示了进展,但总是在整个过程几乎结束之后。主要的问题是当我遇到错误时,我试图将这些错误写入响应中。它们比success字符串长,因为它们包含错误消息

我假设如果我写Response.Flush,它不能100%保证转到客户端,对吗?还是客户本身就是问题所在?如果是客户端,为什么我调用Response.Write太多时服务器会挂起?

EDIT:作为一个附录,如果我将同一段代码放入aspx页面,它就可以工作了。所以我认为这与xhr(XMLHttpRequest(本身有关,它似乎不准备处理流数据。

如果需要,我很乐意提供更多信息。

响应.当我试图同时读取请求和写入响应时,写入挂起

又花了一天的时间,我想我终于明白了。对于那些感兴趣的人,我将在这里发布答案。

首先,我告诉过我的意图是读取流并同时处理csv文件,对吧?

using (var stream = context.Request.GetBufferlessInputStream())
{
}

我没有考虑的第一个问题是,我做每件事都是同步的。所以我只会在处理文件时继续读取流。虽然这是有道理的,但它并不是最佳的,因为我读取文件的速度比分析和更新数据的速度快。

然而,问题在于因此暂停上传过程。在读取整个csv文件之前,我尝试使用Response.Write进行编写。长话短说,在我完全收到请求之前,我正试图发送回复。

我不确定Response.Write在读取整个请求之前执行时的预期行为,但有一件事告诉我,它不可能在客户端为服务器发送信息的同时向客户端发送信息,除非我有某种全双工连接。几个小时前,我看到了这个问题"HTTP管道-每个连接的并发响应",尽管它没有回答我的问题,但这张照片让我很好奇响应是否会与请求一起发生。

然后我随机发现了这个链接,显然来自负责维护和开发HTTP"核心"规范的工作组:在读取所有请求实体之前,是否可以传输响应实体?,基本上说:

是否可以在读取所有请求实体之前传输响应实体

我一直在实现一个HTTP/1.1服务器,它使用请求实体,根据应用程序的需要。

如果应用程序决定可以生成在它完成读取整个请求实体之前的响应是允许吗?

如果状态不是错误代码,答案是什么。服务器可以在所有请求之前开始传输响应实体实体已被读取?(假设服务器足够智能通过总是在请求数据到达时读取请求数据来避免死锁(。

它的回复是一个简短的Yes,但问题是随着电子邮件回复的不断发展而来的。有一件事特别引起了我的注意:server is intelligent enough to avoid deadlock by always reading request data when it arrives。我一直在读:

简而言之,在所有请求之前发送的响应实体的结果实体已被读取是不可预测的。

顺便说一句,纯隧道导致死锁:应用程序可以如果客户端直到传输所有请求,所有TCP窗口都会填满

不可能在某个地方省略缓冲:对于在读取响应之前发送整个请求,整个请求必须缓冲或存储在服务器或应用程序,以解决死锁。

Response.Write似乎被卡住了。

虽然我知道论坛讨论并不是官方文件(甚至是他们自己的论坛讨论(,但我想这给了我解决问题所需的洞察力。

我还试着深入研究.NET代码,仔细检查这是否是问题的根源,但后来遇到了本机调用,我放弃了。

所以后来我把代码改为先上传,然后导入数据并在上传的同时吐出结果,并放了两个进度条:一个用于上传,另一个用于处理。

它运行平稳,工作正常。不再挂起或慢速调用Response.Write

如果我愿意,我可以在上传文件时导入数据,但前提是我在获得所有请求数据后开始编写响应。

谜团解开了,感谢所有读到这个问题的人。我还不会接受我自己的答案,我会等两三天,看看是否有人对这起事件有更好的解释。