HttpWebResponse ReadAsync 超时

HttpWebResponse ReadAsync time out

我尝试使用 await/async:

读取 HttpWebResponse 流
    async static Task testHttpWebClientAsync()        
    {

        string url = "http://localhost/1.txt";
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
        req.Method = "GET";
        HttpWebResponse resp = (HttpWebResponse)await req.GetResponseAsync();            
        Stream stream = resp.GetResponseStream();
        stream.ReadTimeout = 10 * 1000;
        byte[] buffer = new byte[1024];
        while (await stream.ReadAsync(buffer, 0, buffer.Length) > 0)                           
        {
            //time out exception never thrown
        }
    }

但它不起作用,它在 ReadAsync 上永远不会超时。 为了进行比较,非异步版本与同一本地主机测试服务器完美配合:

    static void testHttpWebClient()
    {            
        string url = "http://localhost/1.txt";
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
        req.Method = "GET";
        HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
        Stream stream = resp.GetResponseStream();
        stream.ReadTimeout = 10 * 1000;
        byte[] buffer = new byte[1024];
        while (stream.Read(buffer, 0, buffer.Length) > 0)
        {
            //time out exception thrown here
        }
    }

以上代码在控制台应用程序中测试:

    static void Main(string[] args)
    {
        testHttpWebClient();
        MainAsync(args).GetAwaiter().GetResult();
    }
    async static Task MainAsync(string[] args)
    {
        await testHttpWebClientAsync();            
    }

但这与问题无关,确实我在WinForms项目中发现了问题并创建了控制台项目来测试问题。

供参考,测试服务器代码如下:

                    int c = 10;
                    byte[] ba = new byte[1024];
                    SendHeader(sHttpVersion, sMimeType,(int) ba.Length*c, " 200 OK", ref mySocket);
                    for (int k = 0; k < c; k++)
                    {
                        //set break point here
                        SendToBrowser(ba, ref mySocket);
                    }

SO 上有几个类似的主题,但似乎 none 解决了这个问题。从 API 设计的角度来看,显然没有理由 ReadAsync() 不会像 Read() 那样超时,ReadAsync 只需要同时监视套接字和内部计时器事件,这就是 Task.Delay() 有效。这与 CancellationToken 等无关,因为我们不需要取消任何东西,即使 ReadAsync 也有接受 CancellationToken 的版本。

所以这个问题既是针对问题的解决方案,又是为什么 ReadAsync 没有按预期超时。

HttpWebRequest 上的异步 API(以及 WebClient 上的异步 API,因为它在内部使用 HttpWebRequest)不在内部使用超时。虽然我无法真正解释背后的原因,这是设计使然

这一点尤为明显in the Write logic of the ConnectStream (used internally by HttpWebResponse):

if (async) {
    m_Connection.BeginMultipleWrite(buffers, m_WriteCallbackDelegate, asyncResult);
}
else {
    SafeSetSocketTimeout(SocketShutdown.Send);
    m_Connection.MultipleWrite(buffers);
}

SafeSetSocketTimeout 是负责在底层套接字上设置超时的方法。如您所见,它在异步路径上被故意跳过。读操作也是一样,只是代码比较复杂,比较难展示。

因此,你真的只有两个解决方案:

  1. 自己实现超时(通常使用在 HttpWebRequest 上调用 .Abort() 的计时器)
  2. 改用HttpClient