web-dev-qa-db-ja.com

無限のタイムアウトを持つHttpClientは、タイムアウト例外をスローします

私の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

クライアントのタイムアウトが無限大に設定されていても、接続は最初の出力から約5分後にタイムアウトし(これは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 コードの大まかな適応です(ソースセクションを参照)。

クライアントがタイムアウトするのはなぜですか?これを防ぐにはどうすればよいですか?

私の最終的な目標は、電話をかけ、無期限に応答を待つことです。応答があった場合、将来さらに応答が来る可能性があるため、接続を閉じたくありません。残念ながら、サーバー側では何も変更できません。

7
Ali Zahid

_Stream.CanTimeout_ false のデフォルト値ですが、response.Content.ReadAsStreamAsync()を介してストリームを返すと、CanTimeoutプロパティがtrueを返すストリームが得られます。

このストリームのデフォルトの読み取りおよび書き込みタイムアウト 5分 。つまり、非アクティブ状態が5分間続くと、ストリームは例外をスローします。質問に示されている例外とよく似ています。

この動作を変更するには、ストリームの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によってスローされているように見えた例外が修正されました。

6
Ali Zahid

メソッドがTaskを返すようにします

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

コンソールのmainメソッドをWaitTaskに更新して完了します。

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

私はこの問題を次の方法で解決しました。

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接続からサーバーから送信された通知を受信することがわかったソリューションです。