【问题标题】:HttpWebResponse ReadAsync time outHttpWebResponse ReadAsync 超时
【发布时间】:2019-09-06 13:59:25
【问题描述】:

我尝试使用 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 有几个类似的主题,但似乎没有一个能解决这个问题。从 API 设计的角度来看,显然没有理由 ReadAsync() 不会像 Read() 那样超时,ReadAsync 只需要同时观察套接字和内部计时器事件,这就是 Task.Delay() 的工作原理。这与 CancellationToken 等无关,因为我们不需要取消任何东西,即使 ReadAsync 也有接受 CancellationToken 的版本。

所以这个问题既是针对问题的解决方案,也是为什么 ReadAsync 不会像预期的那样超时。

【问题讨论】:

  • 为什么不对实际请求设置超时时间? req.TimeOut = 10 * 1000?
  • @Çöđěxěŕ 首先,它也不起作用。其次,req.TimeOut 是关于req.GetResponse 的超时时间,不是读操作。确实,req.ReadWriteTimeout 也不起作用。
  • NetworkStream怎么样,你试过吗?
  • 以前从未听说过...让我们专注于上面的代码。
  • 主要问题是因为它返回一个Task,您正在等待这个任务没有取消令牌。您如何期望它超时/取消,并非所有流都支持超时以及我使用NetworkStream提到的原因...

标签: c# async-await stream timeout


【解决方案1】:

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

这一点尤其明显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

【讨论】:

  • 但是为什么 stream.ReadTimeout 不会抛出异常呢?并且似乎没有文档提到这次超时行为。那么这是否被认为是一个错误?
  • stream.ReadTimeout 不会引发异常,因为如果您使用 Read 而不是 ReadAsync 它将起作用(在您设置 ReadTimeout 时还不知道)。而且行为记录在案:docs.microsoft.com/en-us/dotnet/api/…In the case of asynchronous requests, the client application implements its own time-out mechanism.
  • 你能发一个使用 HttpClient 和 await 来实现上述功能的例子吗?
  • “在异步请求的情况下,客户端应用程序实现自己的超时机制。 ” 所以幕后的推理和问题可能是:真正的传统async code会在函数调用返回后自行处理timeout,如doc所说,但是ayns/await是使用sync code sytle来实现async操作,所以API 确实是同步代码,它应该让函数本身处理超时。他们似乎混淆了真正的异步和异步/等待。
  • 因为所有的API一般都使用相同的原理,所以可能不会有一个等待的函数会超时(Task.Delay的目的是超时)。如果是,会不会等待HttpClient的任何API并触发超时?
猜你喜欢
  • 2013-04-29
  • 1970-01-01
  • 1970-01-01
  • 2013-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多