【问题标题】:Response.Write hangs when I try to read from request and write to response at the same time当我尝试同时读取请求并写入响应时,Response.Write 挂起
【发布时间】:2014-08-22 00:23:24
【问题描述】:

问题

这几天让我发疯。我们这里有一个过程,通过驻留在 ASP.NET Web 窗体 (.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 相同。我想我在这里遗漏了一些东西。
  • 我创建了一个简单的网络表单项目来测试我一直在尝试做的事情。它有一个通用处理程序,它将在 10 秒内每秒返回一个数字。它实际上会更新(并不总是以 1 秒的方式更新),我可以看到正在进行的进度。
  • 当我在响应中只写了几行时,它实际上显示了进度,但总是在整个过程几乎完成之后。主要问题是当我遇到错误并尝试将这些写入响应时。它们比 success 字符串长,因为它们包含错误消息。

我假设如果我写Response.Flush 并不能保证 100% 到达客户那里,对吗?还是客户本身的问题?如果是客户端,为什么我调用Response.Write太多时服务器会挂掉?

编辑: 作为附录,如果我将同一段代码放入 aspx 页面,它会起作用。所以我认为这与xhr (XMLHttpRequest) 本身有关,它似乎不准备处理流数据。

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

【问题讨论】:

    标签: c# asp.net csv streaming


    【解决方案1】:

    又在这个问题上猛烈抨击了一天,我想我终于明白了。有兴趣的朋友,我会把答案贴在这里。

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

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

    我没有考虑的第一个问题是我以同步方式执行所有操作。所以我只会在处理文件时继续阅读流。虽然这很有意义,但它并不是最佳选择,因为我读取文件的速度比分析和更新数据的速度要快。

    但是,问题在于挂起上传过程因此。在阅读整个 csv 文件之前,我尝试使用 Response.Write 进行编写。长话短说,我试图在完全得到我的请求之前发送回复。

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

    然后我随机找到了这个链接,显然来自负责维护和开发 HTTP 的“核心”规范的工作组:Can the response entity be transmitted before all the request entity has been read?,它基本上说:

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

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

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

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

    回复了一个简短的Yes,但随着电子邮件回复的进行,问题也随之而来。引起我注意的一件事就是这部分:server is intelligent enough to avoid deadlock by always reading request data when it arrives。我一直在阅读:

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

    顺便说一句,纯隧道会导致死锁:应用程序可以获得 如果客户端在读取响应之前没有读取响应,则会卡住写入 传输所有请求,所有 TCP 窗口都填满

    不可能省略缓冲somewhere:对于客户端 在读取响应之前发送整个请求,整个请求 必须缓冲或存储在某处,无论是在服务器中还是在 应用程序,以解决死锁。

    Response.Write 似乎卡住了。

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

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

    所以后来我改了代码先上传,然后导入数据,边做边吐结果,放了2个进度条:一个用于上传,一个用于处理。

    它运行顺利并按预期工作。不再挂起或缓慢调用Response.Write

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

    谜团解开了,感谢所有阅读问题的人。我还不会接受我自己的答案,我会等待 2 或 3 天,看看是否有人对此事件有更好的解释。

    【讨论】:

      猜你喜欢
      • 2021-12-23
      • 2011-08-10
      • 1970-01-01
      • 2017-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多