无限超时的HttpClient抛出超时异常

11
我的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 的默认超时时间),连接会超时,并抛出以下异常。

尽管客户端的超时时间设置为无限,但在第一次输出大约五分钟后(这不是 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是对代码的粗略适应(请参见源代码部分)。

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

我的最终目标是进行调用并无限期地等待响应。当响应到来时,我不希望连接关闭,因为未来可能还有更多的响应。不幸的是,在服务器端我不能修改任何东西。


2
避免使用 async void。应该使用 Task 定义此方法。同时展示如何调用此方法。 - Nkosi
2
这似乎是一个XY问题。你试图实现的最终目标是什么? - Nkosi
@Nkosi 这个方法在控制台应用程序的主方法中被调用,像这样:ListenForSearchQueries(resourceId); Console.ReadKey(); - Ali Zahid
async void 是“火而忘”的,这可能是导致您问题的原因。您需要使该方法返回一个任务并在任务上调用 wait。更新问题以显示主方法。 - Nkosi
@Nkosi 如果我返回Task并等待它,那么我也必须使主控制台方法异步,这会导致构建失败。错误CS4009:'Program.Main(string [])':入口点不能标记为“async”修饰符 - Ali Zahid
2
你有一个不同的客户端以这种方式与服务器通信的工作示例吗?也许是服务器自带的东西?我想知道超时是否是由于httpclient的某个依赖项而引起的,而你根本无法控制它。甚至可能是操作系统强制超时作为资源管理策略。如果超时发生在可预测的时间段之后,但仍然少于你指定的超时时间,那肯定会指向httpclient中的某些逻辑以外的东西,而这是你能够控制的。 - Rob Hill
3个回答

14
Stream.CanTimeout 的默认值为false,但通过 response.Content.ReadAsStreamAsync() 返回的流将返回 CanTimeout 属性为 true 的流。
该流的默认读取和写入超时时间为5分钟。也就是说,若在5分钟内没有活动,则流会抛出异常,这与问题中显示的异常非常相似。
要更改此行为,可以调整流的 ReadTimeout 和/或 WriteTimeout 属性。
以下是修改版本的 ListenForSearchQueries 方法,将 ReadTimeout 更改为 Infinite。
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
}

更新控制台的主方法,使其等待任务完成。

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

不幸的是,问题仍然存在,出现了相同的异常消息。 - Ali Zahid
@AliZahid 我很好奇。你为什么要使用 HttpClient 来监听查询呢?我再次提到这是一个 XY 问题。 - Nkosi
基本上,我想发起一个调用并无限期地等待响应。当响应到来时,我也不想关闭连接,因为未来可能会有更多的响应。不幸的是,我无法在服务器端进行任何更改。 - Ali Zahid

-2
我用以下方法解决了这个问题:
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文件中,稍后再读取该文件并将其删除。目前这是我找到的接收服务器通过持久HTTP连接发送给我的通知的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接