【问题标题】:C# - StreamReader.ReadToEnd() is extremely slowC# - StreamReader.ReadToEnd() 非常慢
【发布时间】:2015-04-02 11:47:27
【问题描述】:

我正在制作一个 Web Crawler,但我刚刚发现我的方法之一 GetHTML 非常慢,因为它使用 StreamReader 从 HttpWebResponse 对象中获取 HTML 字符串。

方法如下:

static string GetHTML(string URL)
      {
           HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
           Request.Proxy = null;
           HttpWebResponse Response = ((HttpWebResponse)Request.GetResponse());
           Stream RespStream = Response.GetResponseStream();
           return new StreamReader(RespStream).ReadToEnd(); // Very slow
      }

我用 Stopwatch 做了一个测试,并在 YouTube 上使用了这个方法。

Time it takes to get an HTTP response: 500 MS

Time it takes to convert the HttpWebResponse object to a string: 550 MS

所以 HTTP 请求没问题,只是 ReadToEnd() 太慢了。

除了 ReadToEnd() 方法之外,还有其他方法可以从响应对象中获取 HTML 字符串吗?我尝试使用 WebClient.DownloadString() 方法,但它只是 HttpWebRequest 的一个包装器,它也使用流。

编辑:用 Sockets 试了一下,速度要快得多:

static string SocketHTML(string URL)
      {
           string IP = Dns.GetHostAddresses(URL)[0].ToString();
           Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           s.Connect(new IPEndPoint(IPAddress.Parse(IP), 80));
           s.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"));
           List<byte> HTML = new List<byte>();
           int Bytes = 1;
           while (Bytes > 0)
           {
                byte[] Data = new byte[1024];
                Bytes = s.Receive(Data);
                foreach (byte b in Data) HTML.Add(b);
           }
           s.Close();
           return Encoding.ASCII.GetString(HTML.ToArray());
      }

不过,将它与 Socket 一起使用的问题在于,它在大多数情况下会返回错误,例如“永久移动”或“您的浏览器发送了服务器无法理解的请求”。

【问题讨论】:

  • 你在这里比较什么?对远程站点的实际调用返回一个空字符串?
  • 我做了这个比较,看看 StreamReader.ReadToEnd() 是否是瓶颈,我已经看到了。当我收到响应并且我不使用 ReadToEnd() 方法时,GetHTML(string URL) 返回大约需要 500 毫秒,但如果我使用 ReadToEnd() 方法则需要 1000 毫秒。在这种情况下(当我在 youtube.com 上测试时),ReadToEnd() 方法需要 500 毫秒才能完成 - 这非常慢。请求本身没问题,发送OK,但是转成字符串很慢。

标签: .net performance stream streamreader httpwebresponse


【解决方案1】:

当我调用此方法但返回 String.Empty 而不是 ReadToEnd 时,该方法大约需要 500 毫秒。

所有这一切都是 开始 获得响应需要 500 毫秒。调用GetResponseStream 不会消耗所有数据。

ReadToEnd 还将进行从二进制数据到文本的转换,但我怀疑这很重要——我强烈怀疑它只是在等待数据通过网络到达。为了验证这一点,您应该将日志记录添加到代码的各个方面运行Wireshark - 然后您应该能够在数据到达时逐个查看数据包,并将其与日志记录相关联.

作为一个附带问题,您应该绝对有一个using 声明作为响应:

using (var response = ((HttpWebResponse)Request.GetResponse())
{
    // The stream will be disposed when the response is.
    return new StreamReader(response.GetResponseStream())
        .ReadToEnd();
}

如果您不处理响应,您将占用连接,直到垃圾收集器最终确定它们。这可能会导致超时。

【讨论】:

  • 感谢您的回答。使用 TCP 套接字并直接发送“GET / HTTP/1.1\r\n\r\n”数据会更快吗?
  • @BlueRay010:我非常怀疑它——如果只是通过从服务器到客户端获取数据来花费时间的话。自己使用 TCP 会有什么帮助? (这与 HttpRequest 所做的相同。)
  • @BlueRay010:事实上,自己使用套接字会更糟糕,因为这样你就不会获得连接池。但是,您目前遇到了一个错误,因为您没有处理响应 - 请参阅我的编辑。
  • 你说得对,我之前在 HttpWebRequest 中看到过 using 声明,但我认为没有必要。我现在添加了它,它并没有让它更快,但它肯定会防止代码在数千个请求后变慢。
  • @BlueRay010:超时可以很容易地添加到 WebClient。见stackoverflow.com/a/6994391/18192
【解决方案2】:

不是ReadToEnd方法慢,是等待数据需要时间。

ReadToEnd 方法足够快。我刚刚测试了使用流阅读器从内存流中读取一兆字节的数据,只需要 3 毫秒。

当您从请求中获取响应流时,它才刚刚开始获取所请求的数据。一旦你读取了已经收到的数据,它必须等待其余的数据到达。这就是ReadToEnd 通话中需要花费的时间。使用任何其他读取流的方式都不会使其更快。

【讨论】:

  • 好的,谢谢。没有任何方法可以更快地从网站获取 HTML 代码吗?这通常很慢,即使在小型网站中,单个请求也需要数百毫秒。
  • @BlueRay010 那一百毫秒可能是你和服务器之间的延迟。
  • 实际上,我尝试使用 TCP 套接字发送标准的 HTTP GET 请求,发现它花费的时间明显减少(大约是 HttpWebRequest 类的时间的 25%)。我知道延迟,但为什么套接字更快?不过,我不想使用 Sockets,因为我有时会收到诸如“永久移动”之类的响应并且不想处理它。
  • @BlueRay010:通常你不能加快请求速度。您受到响应的服务器以及您与服务器之间的网络的支配。当您使用套接字发送请求时,您会得到相同的响应吗?如果服务器向您发送错误消息,它可能比生成网页要快得多。
  • 是的,我真的没有想过它会更快,因为服务器端错误输出而不是请求的页面。非常感谢!
【解决方案3】:

我做了这个比较,看看StreamReader.ReadToEnd() 是否是瓶颈,我已经看到了。

您在这里得出了错误的结论:瓶颈是整个方法,而不仅仅是它的StreamReader.ReadToEnd() 部分。

当我收到响应并且不使用ReadToEnd() 方法时,大约需要500 MS,但如果我使用ReadToEnd() 方法,则需要1000 MS。

就是这样 - 呼叫Response.GetResponseStream() 的能力并不意味着您“得到了回应”。您得到的只是确认响应存在。

在现实世界中,这类似于收到您必须在邮局签收的包裹。邮局会在你的邮箱里放一张明信片,上面写着邮局有快递等着你。那是您的Response.GetResponseStream() 电话。但此时您没有包裹,只有一张明信片,上面写着包裹在那里。现在你需要去邮局,向他们出示卡片,然后取回包裹。那是StreamReader.ReadToEnd() 电话。

时间几乎翻了一番,因为 1000 毫秒的大部分时间都花在与远程服务器通信上。如果您需要整个响应,那么您几乎无法加快速度。好消息是,由于时间花在 I/O 上,您很有可能能够并行化此代码以从多个网站检索数据(假设您没有将网络加载到最大容量)。

【讨论】:

  • 哦,我明白了,我不太了解输入/输出和流,所以我认为这仅用于转换。我已经有多线程代码,我只是觉得如果我能稍微快一点会更好。谢谢!
猜你喜欢
  • 2015-08-28
  • 2012-04-14
  • 1970-01-01
  • 2022-01-25
  • 2015-05-22
  • 1970-01-01
  • 1970-01-01
  • 2011-09-21
  • 1970-01-01
相关资源
最近更新 更多