【问题标题】:HttpClient with infinite time out throws time out exception无限超时的 HttpClient 抛出超时异常
【发布时间】:2017-08-09 02:39:12
【问题描述】:

我的 HttpClient 使用摘要身份验证连接到服务器并期望搜索查询作为响应。这些搜索查询可以随时出现,因此客户端应始终保持连接打开。

使用以下代码建立连接:

public static async void ListenForSearchQueries(int resourceId)
{
    var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";

    var httpHandler = new HttpClientHandler { PreAuthenticate = true };

    using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
    using (var client = new HttpClient(digestAuthMessageHandler))
    {
        client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);

        var request = new HttpRequestMessage(HttpMethod.Get, url);

        var tokenSource = new CancellationTokenSource();
            tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));

        using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
        {
            Console.WriteLine("\nResponse code: " + response.StatusCode);

            using (var body = await response.Content.ReadAsStreamAsync())
            using (var reader = new StreamReader(body))
                while (!reader.EndOfStream)
                    Console.WriteLine(reader.ReadLine());
         }
    }
}

这是在控制台应用程序的 Main 方法中使用该方法的方式。

private static void Main(string[] args)
{
   const int serviceId = 128;
   .
   .
   .
   ListenForSearchQueries(resourceId);
   Console.ReadKey();
}

控制台窗口的输出如下所示:

Response code: OK
--searchRequestBoundary

即使客户端的超时设置为无穷大,在第一次输出后大约五分钟后连接超时(这不是 HttpClient 的默认超时),并抛出以下异常。

System.IO.IOException occurred
  HResult=0x80131620
  Message=The read operation failed, see inner exception.
  Source=System.Net.Http
  StackTrace:
   at System.Net.Http.HttpClientHandler.WebExceptionWrapperStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Http.DelegatingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.StreamReader.ReadBuffer()
   at System.IO.StreamReader.get_EndOfStream()
   at ConsoleTester.Program.<ListenSearchQueriesDigestAuthMessageHandler>d__10.MoveNext() in C:\Users\xyz\ProjName\ConsoleTester\Program.cs:line 270

Inner Exception 1:
WebException: The operation has timed out.

用于身份验证的 DelegateHandler 是对this 代码的粗略改编(参见源代码部分)。

为什么客户端会超时?如何防止这种情况发生?

我的最终目标是拨打电话并无限期地等待回复。当响应确实到来时,我不希望连接关闭,因为将来可能会出现更多响应。不幸的是,我无法在服务器端更改任何内容。

【问题讨论】:

  • 避免使用async void。此方法应使用Task 定义。还要展示这个方法是如何被调用的。
  • 这似乎是XY problem。您要达到的最终目标是什么?
  • @Nkosi 该方法在控制台应用程序的 main 方法中调用如下:ListenForSearchQueries(resourceId); Console.ReadKey();
  • async void 着火了,忘记这可能是导致您的问题的原因。您需要使该方法返回一个任务并在该任务上调用等待。更新问题以显示主要方法。
  • 您是否有其他客户端以这种方式与服务器通信的工作示例?也许是服务器本身附带的东西?我想知道超时是否是由您根本无法控制的 httpclient 的依赖引起的。甚至可能是操作系统将超时作为资源管理策略。如果超时发生在可预测的时间段之后,但仍小于您为超时指定的时间,那肯定会指向您能够控制的 httpclient 中的逻辑以外的其他内容。

标签: c# http dotnet-httpclient


【解决方案1】:

虽然Stream.CanTimeout is false 的默认值是,通过response.Content.ReadAsStreamAsync() 返回流会给出CanTimeout 属性返回true 的流。

此流is 5 minutes 的默认读写超时。也就是说,在五分钟不活动后,流将引发异常。与问题中显示的异常非常相似。

要更改此行为,可以调整流的 ReadTimeout 和/或 WriteTimeout 属性。

以下是将 ReadTimeout 更改为 Infinite 的 ListenForSearchQueries 方法的修改版本。

public static async void ListenForSearchQueries(int resourceId)
{
    var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";

    var httpHandler = new HttpClientHandler { PreAuthenticate = true };

    using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
    using (var client = new HttpClient(digestAuthMessageHandler))
    {
        client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);

        var request = new HttpRequestMessage(HttpMethod.Get, url);

        var tokenSource = new CancellationTokenSource();
            tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));

        using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
        {
            Console.WriteLine("\nResponse code: " + response.StatusCode);

            using (var body = await response.Content.ReadAsStreamAsync())
            {
                body.ReadTimeout = Timeout.Infinite;

                using (var reader = new StreamReader(body))
                    while (!reader.EndOfStream)
                        Console.WriteLine(reader.ReadLine());
            }
         }
    }
}

这修复了实际上由流引发但似乎是由 HttpClient 引发的异常。

【讨论】:

    【解决方案2】:

    使方法返回Task

    public static async Task ListenForSearchQueries(int resourceId) {
        //...code removed for brevity
    }
    

    Task上更新控制台的main方法为Wait即可完成。

    public static void Main(string[] args) {
       const int serviceId = 128;
       .
       .
       .
       ListenForSearchQueries(resourceId).Wait();
       Console.ReadKey();
    }
    

    【讨论】:

    • 不幸的是,问题仍然存在,并出现相同的异常消息。
    • @AliZahid 不过我很好奇。为什么要使用 HttpClient 来侦听查询?我再次提到这是一个 XY 问题。
    • 基本上我想打电话并无限期地等待回复。我也不想在响应到来时关闭连接,因为将来可能会有更多响应。不幸的是,我无法在服务器端更改任何内容。
    【解决方案3】:

    我通过以下方式解决了这个问题:

    var stream = await response.Content.ReadAsStreamAsync();
        while (b == 1)
        { 
            var bytes = new byte[1];
            try
            {
                var bytesread = await stream.ReadAsync(bytes, 0, 1);
                if (bytesread > 0)
                {
                    text = Encoding.UTF8.GetString(bytes);
    
                    Console.WriteLine(text);
                    using (System.IO.StreamWriter escritor = new System.IO.StreamWriter(@"C:\orden\ConSegu.txt", true))
                    {
                        if (ctext == 100)
                        {
                            escritor.WriteLine(text);
                            ctext = 0;
                        }
                        escritor.Write(text);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("error");
                Console.WriteLine(ex.Message);
            }
        }
    

    通过这种方式,我得到一个字节一个字节的答案,并将它保存在一个 txt 后来我读了txt,我又把它擦掉了。目前,这是我发现的从持久 HTTP 连接接收服务器发送给我的通知的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-16
      • 2011-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多