Runtime: HttpClientはタイムアウト時にTaskCanceledExceptionをスローします

作成日 2017年05月25日  ·  119コメント  ·  ソース: dotnet/runtime

HttpClientは、状況によってはタイムアウト時にTaskCanceledExceptionをスローします。 これは、サーバーに大きな負荷がかかっているときに発生します。 デフォルトのタイムアウトを増やすことで回避できました。 次のMSDNフォーラムスレッドは、発生した問題の本質をキャプチャしています: https ://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw

ありがとう

area-System.Net.Http enhancement

最も参考になるコメント

@karelzは、この誤解を招く例外の影響を受けた別の顧客としてメモを追加します:)

全てのコメント119件

@awithyどのバージョンの.NETCoreを使用していますか?

1.1

正しい設計であるかどうかにかかわらず、設計上、タイムアウトに対してOperationCanceledExceptionsがスローされます(およびTaskCanceledExceptionはOperationCanceledExceptionです)。
cc:@davidsh

私たちのチームはこれが直感的ではないと感じましたが、設計どおりに機能しているようです。 残念ながら、これに遭遇したとき、キャンセルの原因を見つけるために少し時間を無駄にしました。 このシナリオをタスクのキャンセルとは異なる方法で処理する必要があるのはかなり醜いです(これを管理するためにカスタムハンドラーを作成しました)。 これも概念としてのキャンセルの乱用のようです。

迅速な対応に改めて感謝いたします。

設計によるもののようです+過去6か月以上に苦情を申し立てた顧客は2人だけでした。
閉鎖。

@karelzは、この誤解を招く例外の影響を受けた別の顧客としてメモを追加します:)

情報をありがとう、トップポストにも賛成してください(問題はそれによってソートすることができます)、ありがとう!

正しい設計であるかどうかにかかわらず、設計により、タイムアウトに対してOperationCanceledExceptionsがスローされます

これは悪いデザインのIMOです。 リクエストが実際にキャンセルされたか(つまり、 SendAsyncに渡されたキャンセルトークンがキャンセルされたか)、またはタイムアウトが経過したかどうかを判断する方法はありません。 後者の場合、再試行するのは理にかなっていますが、前者の場合はそうではありません。 この場合に最適なTimeoutExceptionがあります。それを使用してみませんか?

ええ、これはまた私たちのチームをループに投げ込みました。 それに対処するための専用のStackOverflowスレッド全体があります: https ://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out/32230327#32230327

数日前に回避策を公開しました: https ://www.thomaslevesque.com/2018/02/25/better-timeout-handling-with-httpclient/

素晴らしいです、ありがとう。

関心が大きくなったので再開します-StackOverflowスレッドには69の賛成票があります。

cc @ dotnet / ncl

連絡あった ? dotnet core2.0でも発生します

これは、2.1以降の上位の問題のリストに含まれています。 ただし、2.0 /2.1では修正されません。

タイムアウトが発生したときに何をするかについて、いくつかのオプションがあります。

  1. $#$ TaskCanceledException TimeoutExceptionを投げます。

    • 長所:実行時のタイムアウトと明示的なキャンセルアクションを簡単に区別できます

    • 短所:技術的な重大な変更-新しいタイプの例外がスローされます。

  2. TimeoutExceptionとして内部例外を除いてTaskCanceledExceptionをスローします

    • 長所:実行時のタイムアウトと明示的なキャンセルアクションを区別できます。 互換性のある例外タイプ。

    • 未解決の質問: InnerExceptionTimeoutException.InnerExceptionとして)を保持しながら、元のTaskCanceledExceptionスタックを破棄して新しいスタックを破棄しても大丈夫ですか? または、元のTaskCanceledExceptionを保存してラップする必要がありますか?



      • 次のいずれか:新しいTaskCanceledException->新しいTimeoutException->元のTaskCanceledException(nullの可能性がある元のInnerExceptionを含む)


      • または:新しいTaskCanceledException->新しいTimeoutException->元のTaskCanceledException.InnerException(nullの場合があります)



  3. 理由としてタイムアウトを示すメッセージを使用してTaskCanceledExceptionをスローします

    • 長所:メッセージ/ログからの明示的なキャンセルアクションからタイムアウトを区別することが可能

    • 短所:実行時に区別できません。「デバッグのみ」です。

    • 未解決の質問:[2]と同じ-元のTaskCanceledException(およびスタック)をラップまたは置換する必要があります

元のTaskCanceledExceptionの元のスタックを破棄して、オプション[2]に傾いています。
@stephentoub @davidsh何か考えはありますか?

ところで:タイムアウトを設定したHttpClient.SendAsyncでは、変更はかなり簡単なはずです。
https://github.com/dotnet/corefx/blob/30fb78875148665b98748ede3013641734d9bf5c/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L433 -L461

トップレベルの例外は常にHttpRequestExceptionである必要があります。 これは、HttpClientの標準APIモデルです。

その中で、内部例外として、「タイムアウト」はおそらくTimeoutExceptionであるはずです。 実際、これを他の問題(WebExceptionStatusと同様の新しい列挙型プロパティを含めるようにHttpRequestionを変更することについて)と組み合わせる場合、開発者はその列挙型をチェックするだけでよく、内部例外をまったく検査する必要はありません。

これは基本的にオプション1)ですが、HttpClientAPIからのトップレベルの例外は常にHttpRequestExceptionであるという現在のアプリ互換APIモデルを引き続き保持します。

注:ストリームAPIからHTTP応答ストリームを直接読み取ることによる「タイムアウト」は次のようになります。

`` `c#
var client = new HttpClient();
ストリームresponseStream = await client.GetStreamAsync(url);

int bytesRead = await responseStream.ReadAsync(buffer、0、buffer.Length);
`` `

引き続きSystem.IO.IOExceptionなどをトップレベルの例外としてスローする必要があります(実際には、これに関してSocketsHttpHandlerにいくつかのバグがあります)。

@davidshは、明示的なユーザーキャンセルがある場合でも、 HttpRequestExceptionをスローすることを提案していますか? それらは今日TaskCanceledExceptionを投げます、そしてそれは大丈夫のようです。 それが私たちが変えたいものかどうかわからない...
クライアント側のタイムアウトを$# TaskCanceledException #$ではなくHttpRequestExceptionとして公開してもかまいません。

@davidsh明示的なユーザーキャンセルがある場合でも、HttpRequestExceptionをスローすることを提案していますか? それらは今日TaskCanceledExceptionをスローし、それは問題ないようです。 それが私たちが変えたいものかどうかわからない…

この質問に答える前に、.NET Frameworkを含むスタックのさまざまな動作をテストして、現在の正確な動作を確認する必要があります。

また、場合によっては、スタックの一部がHttpRequestExceptionをスローし、OperationCanceledExceptionの内部例外が発生します。 TaskCanceledExceptionは、OperationCanceledExceptionの派生型です。

明示的なユーザーキャンセルがある場合

また、「明示的」とは何かを明確に定義する必要があります。 たとえば、HttpClient.Timeoutプロパティを「明示的」に設定していますか? いいえと言っていると思います。 CancellationTokenSourceオブジェクト(渡されたCancellationTokenオブジェクトに影響します)で.Cancel()を呼び出すのはどうですか? それは「明示的」ですか? おそらく他にも考えられる例があります。

HttpClient.Timeoutを設定すると、.NETFrameworkもTaskCanceledExceptionをスローします。

クライアントからのキャンセルと実装からのキャンセルを区別できない場合は、両方を確認してください。 クライアントトークンを使用する前に、派生トークンを作成します。
`` `c#
var clientToken =...。
var innerTokenSource = CancellationTokenSource.CreateLinkedTokenSource(clientToken);

If at some point you catch **OperationCancelledException**:
```c#
try
{
     //invocation with innerToken
}
catch(OperationCancelledException oce)
{
      if(clientToken.IsCancellationRequested)
          throw; //as is, it is client initiated and in higher priority
      if(innerToken.IsCancellationRequested)
           //wrap it in HttpException, TimeoutException, whatever, wrap it in another linked token until you process all cases.
}

基本的に、キャンセルの原因となったトークンが見つかるまで、クライアントトークンから最も深いレイヤーまですべての可能なレイヤーをフィルタリングします。

2.2または3.0を作成するための更新はありますか? @karelz @ davidsh 、cc @ NickCraver 。 オプション1)TimeoutExceptionをスローするか、あるいはHttpRequestExceptionから派生した新しいHttpTimeoutExceptionをスローするだけでよいと思います。 ありがとう!

2.2または3.0を作成するための更新はありますか?

2.2には遅すぎて、先週リリースされました;)

2.2には遅すぎて、先週リリースされました;)

ああ、それは私が休憩したときに起こることです;)

少し間抜けですが、私が見つけた最も単純な回避策は、OperationCanceledExceptionのcatchハンドラーをネストしてから、IsCancellationRequestedの値を評価して、キャンセルトークンがタイムアウトまたは実際のセッションの中止の結果であるかどうかを判断することです。 明らかに、API呼び出しを実行するロジックは、ビジネスロジックまたはサービスレイヤーにある可能性がありますが、簡単にするために例を統合しました。

`` `` csharp
[HttpGet]
publicasyncタスクIndex(CancellationToken cancelToken)
{{
試す
{{
//これは通常、コントローラー自体ではなく、ビジネスロジックまたはサービスレイヤーで実行されますが、ここでは簡単にするためにポイントを示しています。
試す
{{
// HttpClientインスタンスにHttpClientFactoryを使用する
var httpClient = _httpClientFactory.CreateClient();

        //Set an absurd timeout to illustrate the point
        httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

        //Perform call that requires special timeout logic                
        var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

        //... (if GetAsync doesn't fail, handle the response as desired)
    }
    catch (OperationCanceledException innerOperationCanceled)
    {
        //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
        if (cancellationToken.IsCancellationRequested)
        {
            //Bubble exception to global handler
            throw innerOperationCanceled;
        }
        else
        {
            //... (perform timeout logic here)
        }                    
    }                

}
catch (OperationCanceledException operationCanceledEx)
{
    _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
    return new StatusCodeResult(499);
}
catch (Exception ex)
{
    _logger.LogError(ex, "An unexepcted error occured.");
    return new StatusCodeResult(500);
}

}
`` ``

このシナリオをよりエレガントに処理する方法に関する他の記事を見ましたが、それらはすべてパイプラインなどを掘り下げる必要があります。将来的に実際のタイムアウト例外のサポートが改善されることを期待して、このアプローチは完璧ではないことを認識しています。リリース。

httpクライアントでの作業はまったくひどいものです。 HttpClientを削除して、最初からやり直す必要があります。

少し間抜けですが、私が見つけた最も単純な回避策は、OperationCanceledExceptionのcatchハンドラーをネストしてから、IsCancellationRequestedの値を評価して、キャンセルトークンがタイムアウトまたは実際のセッションの中止の結果であるかどうかを判断することです。 明らかに、API呼び出しを実行するロジックは、ビジネスロジックまたはサービスレイヤーにある可能性がありますが、簡単にするために例を統合しました。

[HttpGet]
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
  try
  {
      //This would normally be done in a business logic or service layer rather than in the controller itself but I'm illustrating the point here for simplicity sake
      try
      {
          //Using HttpClientFactory for HttpClient instances      
          var httpClient = _httpClientFactory.CreateClient();

          //Set an absurd timeout to illustrate the point
          httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

          //Perform call that requires special timeout logic                
          var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

          //... (if GetAsync doesn't fail, handle the response as desired)
      }
      catch (OperationCanceledException innerOperationCanceled)
      {
          //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
          if (cancellationToken.IsCancellationRequested)
          {
              //Bubble exception to global handler
              throw innerOperationCanceled;
          }
          else
          {
              //... (perform timeout logic here)
          }                    
      }                

  }
  catch (OperationCanceledException operationCanceledEx)
  {
      _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
      return new StatusCodeResult(499);
  }
  catch (Exception ex)
  {
      _logger.LogError(ex, "An unexepcted error occured.");
      return new StatusCodeResult(500);
  }
}

このシナリオをよりエレガントに処理する方法に関する他の記事を見ましたが、それらはすべてパイプラインなどを掘り下げる必要があります。将来的に実際のタイムアウト例外のサポートが改善されることを期待して、このアプローチは完璧ではないことを認識しています。リリース。

この凶悪なコードを書く必要があるのは、絶対にばかげたことです。 HttpClientは、Microsoftがこれまでに作成した中で最もリークの多い抽象化です。

タイムアウトに関するより具体的な例外情報を含めないのは、かなり近視眼的であるように思われることに同意します。 キャンセルされたトークンの例外とは異なる方法で実際のタイムアウトを処理したいという人にとっては、かなり基本的なことのようです。

httpクライアントでの作業はまったくひどいものです。 HttpClientを削除して、最初からやり直す必要があります。\

これは、あまり役に立たない、または建設的なフィードバック@dotnetchrisではありません。 顧客とMSFTの従業員はすべて、物事を改善するためにここにいます。そのため、 @ karelzのような人々は、顧客のフィードバックに耳を傾け、改善しようとしています。 彼らが野外で開発を行う必要はありません。彼らの善意を否定的に燃やさないようにしましょう。そうしないと、彼らはボールを持って家に帰ることを決めることができます。それはコミュニティにとってさらに悪いことです。 ありがとう! :)

期待される動作をするHttpClient2(仮の名前)を作成し、ドキュメントで「HttpClientは廃止されました。HttpClient2を使用してください」と通知し、2つの違いを説明してみませんか。

これにより、.NetFrameworkに重大な変更が挿入されなくなります。

HttpClientは、例外のスローに基づくべきではありません。 インターネットホストが利用できないことも例外ではありません。 アドレスの順列が無限であることを考えると、実際には予想されますが、例外的なケースは、機能するものを提供することです。

私は、HttpClientの使用が3年以上前にどれほど悪いかについてさえブログに書きました。 https://dotnetchris.wordpress.com/2015/12/11/working-with-httpclient-is-absurd/

これは明らかに悪いデザインです。

マイクロソフトの誰かがHttpClientを使用したことはありますか? それは確かにそれのようには見えません。

次に、この恐ろしいAPIサーフェスを超えて、その構造とスレッド化に関するあらゆる種類のリークのある抽象化があります。 私はそれが救済可能である可能性があるとは信じられません、そして真新しいだけが実行可能な解決策です。 HttpClient2、HttpClientSlimは問題ありません。

Httpアクセスで例外がスローされることはありません。 常に結果を返す必要があります。結果は、完了、タイムアウト、http応答について検査できる必要があります。 スローを許可する必要がある唯一の理由は、 EnsureSuccess()を呼び出す場合、世界中で例外をスローしても問題ないということです。 ただし、インターネットの完全に正常な動作(ホストが起動している、ホストが起動していない、ホストがタイムアウトしている)の場合、制御フローの処理はゼロである必要があります。 それらのどれも例外ではありません。

HttpClientは、例外のスローに基づくべきではありません。 インターネットホストが利用できないことも例外ではありません。 アドレスの順列が無限であることを考えると、実際には予想されますが、例外的なケースは、機能するものを提供することです。

@dotnetchrisは、このトピックに非常に情熱を持っているように思われるので、時間をかけてHttpClient2用に提案された完全なAPIを作成し、新しい問題としてチームに提出して議論してみませんか?

私は、HttpClientの良し悪しを技術レベルの詳細から理解することに悩まされていないので、@ dotnetchrisのコメントに追加するものは何もありません。 MS開発チームを批判することは絶対に適切ではないと思いますが、処理が非常に簡単であると思われる状況の解決策を研究するために何時間も費やすことはどれほど苛立たしいことでもあります。 特定の決定/変更が行われた理由を理解するのに苦労しているのはフラストレーションであり、MSの誰かがあなたの仕事を終わらせ、その間に提供しようとしている間に懸念に対処することを望んでいます。

私の場合、OAuthプロセス中に呼び出してユーザーの役割を確立する外部サービスがあり、その呼び出しが指定された期間内にタイムアウトした場合(残念ながら、希望以上に発生します)、2番目の方法に戻す必要があります。

本当に、タイムアウトとキャンセルを区別できるようにしたい理由が他にも無数にあります。 このような一般的なシナリオに対するより良いサポートが存在しないように思われる理由は、私が理解するのに苦労していることであり、MSチームがさらに考えてくれることを心から願っています。

@karelz誰かからのAPI提案である3.0のスケジュールでこれを取得するには何が必要ですか?

これはAPIの提案ではありません。 これは、適切な実装を見つけることです。 これは3.0のバックログにあり、リストのかなり上位にあります(HTTP / 2およびエンタープライズシナリオの背後にあります)。 私たちのチームが3.0で対処するのは非常に良いチャンスです。
もちろん、コミュニティの誰かがやる気があれば、私たちも貢献します。

@karelzこの場合、コミュニティはどのように支援できますか? APIの提案が必要かどうかを述べた理由は、「タイムアウトが発生したときに何をすべきか」というメッセージから、チームがリストしたものから優先する代替案を決定したかどうかわからなかったためです。 あなたが方向を決定したなら、私たちはそこから行くことができます。 ありがとう!

技術的に言えば、API提案の質問ではありません。 この決定にはAPIの承認は必要ありません。 ただし、違いを明らかにする方法について調整する必要があることに同意します。議論が行われる可能性があります。
実装においてオプションが互いに大きく異なることはないと思います-コミュニティが支援したい場合、オプションのいずれかの実装を取得すると、98%がそこに到達します(例外が1つの場所からスローされ、上記の[1]-[3]オプションに適応するように後で簡単に変更できます)。

こんにちは! 最近、この問題も発生しました。 それがまだ3.0の間あなたのレーダーにあるかどうかだけ興味がありますか?

はい、それはまだ3.0リストにあります-3.0で対処される可能性は90%です。

私は今日これに出くわしました。 HttpClientにタイムアウトを設定すると、これが表示されます

{System.Threading.Tasks.TaskCanceledException: A task was canceled.}
CancellationRequested = true

タイムアウトが問題であるとは言及されていないため、これは混乱を招くことに同意します。

うーん、わかりました。少し混乱しています。 このスレッドの誰もがHTTPタイムアウトがTaskCanceledExceptionをスローすると言っているようですが、それは私が得ているものではありません...私は.NET Core 2.1でOperationCanceledExceptionを取得しています...

タイムアウトに対してOperationCanceledExceptionをスローしたい場合は、それを回避できますが、適切に文書化されていないため、何が起こっているのかを追跡するのに何時間も費やしました。

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

HttpRequestExceptionがタイムアウトのためにスローされることを明確に示しています...

TaskCanceledExceptionは、OperationCanceledExceptionから派生します。

@stephentoubはい、しかしTaskCanceledExceptionを取得していません、 OperationCanceledExceptionを取得しています。

のように、 TaskCanceledExceptionをキャッチするcatchブロックは例外をキャッチしません。特に、 OperationCanceledExceptionのcatchブロックが必要です。

私は現在、例外のタイプでフィルタリングすることによってHTTPタイムアウトを具体的にテストしています。タイムアウトであることがわかる方法は、それがTaskCanceledExceptionでない場合です。

`` `c#
試す {
using(var response = await s_httpClient.SendAsync(request、_updateCancellationSource.Token).ConfigureAwait(false))
return(int)response.StatusCode <400;
}
catch(OperationCanceledException ex)when(ex.GetType()!= typeof(TaskCanceledException)){
// HTTPタイムアウト
falseを返します。
}
catch(HttpRequestException){
falseを返します。
}

or alternatively this works as well, which might be better:

```c#
            try {
                using (var response = await s_httpClient.SendAsync(request, _updateCancellationSource.Token).ConfigureAwait(false))
                    return (int)response.StatusCode < 400;
            }
            catch (OperationCanceledException ex) when (ex.GetType() == typeof(OperationCanceledException)) {
                // HTTP timeout
                return false;
            }
            catch (HttpRequestException) {
                return false;
            }

うーん、わかりました。少し混乱しています。 このスレッドの誰もがHTTPタイムアウトがTaskCanceledExceptionをスローすると言っているようですが、それは私が得ているものではありません...私は.NET Core 2.1でOperationCanceledExceptionを取得しています...

タイムアウトに対してOperationCanceledExceptionをスローしたい場合は、それを回避できますが、適切に文書化されていないため、何が起こっているのかを追跡するのに何時間も費やしました。

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

HttpRequestExceptionがタイムアウトのためにスローされることを明確に示しています...

タイムアウトを明示的に処理する方法(私が望むほどきれいではありませんが、それは方法です)を示す私が提起したコード例を含む、OperationCanceledExecptionが参照される実際の応答がいくつかあるため、スレッド全体を読みましたか?

@Hobrayはい、私は持っています-私がそうしなかったとあなたが思う理由は何ですか? それでも、このスレッドの何人の人がタイムアウトでTaskCanceledExceptionを受け取っていると思われるかは説明されていませんが、私はそうではありません。

あなたのコード例も見ましたが、残念ながら、あなたが考慮していないような競合状態があります。 私が使用しているコードは、この問題を回避し、キャンセルトークンにアクセスする必要がないことを示しています。これは、タイムアウトをさらに上流でキャッチしようとしている場合に重要になる可能性があります。

@mikernetたぶんその言葉遣いは私を失望させた。 競合状態の可能性を考慮していなかったのは正しいです。 それは素晴らしい解決策ではないことは承知していますが、私が見落としていたことを説明したい場合は、それを聞きたいと思います。

私が気づいたことの1つは、コントローラーとミドルウェアの呼び出しでタイムアウトまたはキャンセルが発生するかどうかによって、発生する正確な例外が異なるように見えることです。 ミドルウェア呼び出しの内部では、 TaskCanceledExceptionのようです。 残念ながら、その理由を理解するために.NET Coreコードライブラリを掘り下げる時間がありませんが、それは私が気付いたものです。

@Hobray Hahaええ、もちろん-申し訳ありませんが、競合状態について謎めいたつもりはありませんでした。外出先で最後のメッセージを入力しているときに電話をしているだけで、説明が難しくなりました。

ソリューションの競合状態は、 HttpClientタイムアウトとキャンセルトークンのキャンセル状態を確認するポイントの間にタスクのキャンセルが開始された場合に発生します。 私の状況では、アップストリームコードがユーザーのキャンセルを検出できるように、バブリングを続けるためにタスクキャンセル例外が必要です。したがって、実際にタイムアウトした場合にのみ例外をキャッチできる必要があります。そのため、 whenを使用しています。キャッチをフィルタリングします。 トークンをチェックするソリューションを使用すると、未処理のOperationCanceledExceptionがフィルターを通過し、バブリングしてプログラムをクラッシュさせる可能性があります。これは、アップストリームで操作をキャンセルする人がTaskCanceledExceptionがスローされることを期待しているためです。 OperationCanceledException

HttpClientメソッドを呼び出すコードがタイムアウトとタスクキャンセルの両方を処理することになっている場合、状況によっては、競合状態が発生したときのタイムアウトとユーザーキャンセルの区別は重要でない場合があります...ただし、ライブラリレベルのコードを記述していて、タスクのキャンセルをより高いレベルで処理するためにバブルアップさせる必要がある場合は、間違いなく違いが生じます。

あなたが言っていることが真実であり、タイムアウトが呼び出しコンテキストが何であるかに応じて異なる例外タイプをスローする場合、それは間違いなく対処する必要がある問題のように思えます。 意図された動作である、または意図された動作であるはずの方法はありません。

考えられる競合状態について興味深い。 私はそれを考えていませんでした。 幸いなことに、私のユースケースでは、それほど問題にはなりません。

例外のバリエーションに関しては、デバッグ中に数回見たものと一致するかどうかを確認するために、作業中のミドルウェアを理解するための時間を見つける必要があります。 愚かなChromeオムニボックスのプリロード呼び出しは、タイミングがちょうど良かったときにミドルウェアで見た場所です。 簡単な例を作成して、何らかの方法でそれを明確に証明できると思います。

正しい回避策がどうあるべきかについては、まだはっきりしていません。

私の解決策は、httpタイムアウトがOperationCanceledExceptionをスローする状況で最も正しいです。 TaskCanceledExceptionがスローされ、その動作を再現する方法を誰も提供していない状況を再現することはできません。そのため、環境をテストし、それが私の環境と一致する場合は、それが正しい解決策です。

一部の実行コンテキストでTaskCanceledExceptionがスローされた場合、それらのコンテキストの汎用ソリューションとして私のソリューションは失敗し、提案された他のソリューションは「最良の」ソリューションですが、状況にとって重要であるかどうかわからない競合状態が含まれています。あなたが評価します。

TaskCanceledExceptionsが発生していると主張している人から詳細を聞きたいのですが、それは明らかに大きなバグになるからです。 私はこれが実際に起こっていることに少し懐疑的です。

@mikernetがTaskCanceledExceptionまたはOperationCanceledException $のどちらがスローされるかは、ほとんど関係ありません。 これは、信頼してはいけない実装の詳細です。 Hobrayのコメント(https://github.com/)で説明されているように、 OperationExceptionTaskCanceledExceptionもキャッチします)をキャッチし、渡したキャンセルトークンがキャンセルされているかどうかを確認します。 dotnet / corefx / issues / 20296#issuecomment-449510983)。

@thomaslevesqueそのアプローチが問題になる理由についてはすでに説明しました。 これに対処しないと、コメントはあまり役に立ちません。説明した問題に対処せずに、変換に参加する前にステートメントを再ハッシュするだけです。

「これは実装の詳細です」についてはそうですが、残念ながら、タスクが実際にキャンセルされたかタイムアウトしたかを判断する良い方法がないため、これに頼らなければなりません。 説明されている競合状態を回避するために、これら2つのシナリオのどちらが発生したかを例外から判断する方法が100%あるはずです。 OperationCanceledExceptionは、このために適切な選択ではありません...フレームワークにはすでにTimeoutExceptionがあり、より良い候補になります。

非同期タスクの例外は、例外の原因に基づいて分岐を決定することが一般的なユースケースである場合、例外の原因を特定するために非同期で変更できる別のオブジェクトのデータのチェックに依存するべきではありません。 それは競合状態の問題を懇願しています。

また、例外の種類が実装の詳細であるという点では...少なくとも、確立された.NETドキュメントの慣例に関しては、例外についてはそうではないと思います。 .NET Frameworkのもう1つの例を挙げてください。ドキュメントには、「Yが発生すると例外Xがスローされる」と記載されており、フレームワークは、まったく異なる例外ケース使用される公的にアクセス可能なサブクラスは言うまでもなく、その例外の公的にアクセス可能なサブクラスを実際にスローします。同じ方法! これは悪い形のように思えますが、15年間の.NETプログラミングでこれに遭遇したことはありません。

とにかく、 TaskCanceledExceptionは、タイムアウトではなく、ユーザーが開始したタスクのキャンセル用に予約する必要があります。前述のように、その例外はすでにあります。 ライブラリは、内部タスクのキャンセルをキャプチャし、より適切な例外をスローする必要があります。 タスクキャンセルトークンで開始されたタスクキャンセルの例外ハンドラーは、非同期操作中に発生する可能性のある他の無関係な問題の処理に責任を負わないようにする必要があります。

@mikernet申し訳ありませんが、私は議論のその部分を逃しました。 はい、私が考えていなかった競合状態があります。 しかし、見た目ほど大きな問題ではないかもしれません...タイムアウトが発生した後、トークンの状態を確認する前にキャンセルトークンが通知されると、競合状態が発生します。 この場合、とにかく操作がキャンセルされていても、タイムアウトが発生したかどうかは問題ではありません。 したがって、 OperationCanceledExceptionバブルアップさせて、操作が正常にキャンセルされたと発信者に信じ込ませます。これは、実際にはタイムアウトが発生したための嘘ですが、発信者はとにかく操作を中止します。

説明されている競合状態を回避するために、これら2つのシナリオのどちらが発生したかを例外から判断する方法が100%あるはずです。 OperationCanceledExceptionは、このために適切な選択ではありません...フレームワークにはすでにTimeoutExceptionがあり、より良い候補になります。

私はそれに完全に同意します、現在の振る舞いは明らかに間違っています。 私の意見では、それを修正するための最良のオプションは、 @ karelzコメントからのオプション1です。 はい、これは重大な変更ですが、ほとんどのコードベースがこのシナリオを正しく処理していないと思われるため、状況が悪化することはありません。

@thomaslevesqueはい、でも私はそれに対処しました。 一部のアプリケーションでは重要な場合と重要でない場合があります。 私の場合はそうです。 私のライブラリメソッドのユーザーは、タスクをキャンセルするときにOperationCanceledExceptionを期待していません。彼らはTaskCanceledExceptionを期待しており、それをキャッチして「安全」である必要があります。 キャンセルトークンを確認し、キャンセルされたことを確認して例外を渡すと、ハンドラーによってキャッチされず、プログラムがクラッシュする例外が渡されました。 私のメソッドはタイムアウトをスローすることは想定されていません。

確かに、これを回避する方法はあります...新しいラップされTaskCanceledExceptionをスローすることはできますが、トップレベルのスタックトレースが失われ、HTTPタイムアウトはサービスのタスクキャンセルとはかなり異なる方法で処理されます-HTTPタイムアウトは長い間キャッシュに入れられますが、タスクのキャンセルはされません。 競合状態が原因で実際​​のタイムアウトをキャッシュすることを見逃したくありません。

OperationCanceledExceptionをキャッチして、キャンセルトークンを確認できると思います。キャンセルされた場合は、例外の種類もチェックして、プログラム全体がクラッシュしないように、再スローする前にTaskCanceledExceptionであることを確認してください。 これらの条件のいずれかがfalseの場合、タイムアウトである必要があります。 その場合に残された唯一の問題は、微妙な競合状態ですが、タイムアウトが設定された高負荷のサービスで1分あたり数千のHTTPリクエストが発生すると、それが発生します。

いずれにせよ、それはハッキーであり、あなたが示したように、何らかの方法で解決する必要があります。

(競合状態は、 OperationCanceledException TaskCanceledExceptionがスローされる環境でのみ発生しますが、少なくともソリューションは、予期しない例外タイプをバブリングすることから安全であり、発生しません。実装の詳細が変更された場合でも合理的に機能するために、実装の詳細に依存します)。

httpタイムアウトの場合にHttpClientによってスローされるタイムアウト例外は、一般的なSystem.TimeoutExceptionではなく、タイプSystem.Net.HttpRequestException (または子孫?)であってはなりません。 たとえば、古き良きWebClientは、$ WebExceptionStatus.Timeoutに設定できるStatusプロパティを持つWebExceptionをスローします。

残念ながら、3.0ではこれを完了できません。 未来への移行。
それはまだバックログの一番上にありますが、現実的には3.0ではそれを行うことができません:(。

@karelz oh no :( 2番目の会社でこれに再び遭遇した方法を言って再投稿するつもりでした。自分で取り組みたい場合、3.0のPRを提出する期間はどれくらいですか?乾杯

上記のコメントの束で何が起こっているのかを正確に追跡するのに苦労しています。コンテキストに応じてTaskCancelledExceptionとOperationCancelledExceptionの取得の違いを報告している人がいますが、MSFTの誰かがそれを明らかにできますか?

@mikernetからのこのコメントのように:

のように、TaskCanceledExceptionをキャッチするcatchブロックは例外をキャッチしません。特に、OperationCanceledExceptionのcatchブロックが必要です。

cc @davidsh @stephentoub多分?

TaskCanceledExceptionは、OperationCanceledExceptionから派生し、少しの追加情報を伝達します。 OperationCanceledExceptionをキャッチするだけです。

@karelzは、最初に投稿したオプション1を、タイムアウト例外をスローさせるだけで、完全に口に合わないでしょうか?

StackExchange.Redisのようなものは、タイムアウト例外から派生した例外をスローします。そのため、HttpClientのタイムアウト例外をキャッチするだけでよいのです...

継承を考えると@stephentoubを理解していますが、 @ mikernetからのこのコメントだけが私の心を吹き飛ばしています...彼が説明するこの動作は、ある種のバグである必要がありますか? ただし、trueの場合、修正または回避策に影響します…

「TaskCanceledExceptionをキャッチするcatchブロックは例外をキャッチしません。特に、OperationCanceledExceptionのcatchブロックが必要です。」

それとも、OperationExをスローする場所とTaskExをスローする場所があると言っていますか? おそらく人々はASPミドルウェアから何かが来ているのを見て、それがHttpClientからのものだと思っているのでしょうか?

@mikernetからの以下のコメントだけが私の心を吹き飛ばしています...彼が説明するこの動作は、ある種のバグである必要がありますか?

なぜそれが「あなたの心を吹き飛ばす」のですか? 例外Bが例外Aから派生している場合、Bのcatchブロックは、具体的にはAである例外をキャッチしません。たとえば、ArgumentNullExceptionのcatchブロックがある場合、「throw new ArgumentException(...)」はキャッチされません。

それとも、OperationExをスローする場所とTaskExをスローする場所があると言っていますか?

右。 一般に、コードはOCEのみを想定する必要があります。 記述方法によっては、一部のコードが派生TCEをスローする可能性があります。たとえば、TaskCompletionSource.TrySetCanceledを介して完了したタスクは、TaskCanceledExceptionを生成します。

スティーブンに感謝します。スレッド内の音から、複数の人がTaskCEがいたるところに投げられているという印象を受けました。これにより、mikernetが言及した動作は意味をなさなくなりました。論理的な観察:)

@stephentoub 、このガイダンスはどこかに文書化されていますか? @guardrexにpingを送信

一般に、コードはOCEのみを想定する必要があります...

最近、ASP.NETドキュメントセットを使用しています。 dotnet / docscatsの問題を開きます...

https://github.com/dotnet/docs/issues

@karelz@davidshの具体的な提案があったところで、この会話を再開することは可能ですか? HttpClient APIモデルの所有権を持つ誰かが、進むべき方向について決定を下す必要があるように感じます。 トップレベルの例外が常にHttpRequestException(明示的なキャンセル以外??)であるという議論があり、WebExceptionStatusに似た新しい列挙型プロパティを含めるようにHttpRequestionを変更するためのより大きなリファクタリングに関する長期的なトピックと混ざり合っていました...

この問題全体が、必要な実際のコード変更に比べて問題になっているように感じます。 いくつかの決定を行うためにループインする必要があるHttpClientAPIモデルの利害関係/所有権を持っている他の人々はいますか? たくさんありがとう!

ここでも同じ問題が発生していますが、 cancellationToken.IsCancellationRequestedtrueを返します:

    public class TimeoutHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                var response = await base.SendAsync(request, cancellationToken);
                response.EnsureSuccessStatusCode();

                return response;
            }
            catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                throw new TimeoutException("Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.");
            }
        }

タイムアウトを強制しています。 発生した例外はTaskCancelledExceptionで、内部例外としてIOExceptionがあります。

System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo. ---> System.Net.Sockets.SocketException: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo
   --- End of inner exception stack trace ---
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token)
   at System.Net.Http.HttpConnection.FillAsync()
   at System.Net.Http.HttpConnection.ReadNextResponseHeaderLineAsync(Boolean foldedHeadersAllowed)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

...

(翻訳された例外テキストについては申し訳ありません-どうやら誰かがこれを.NET Coreにも持ってくるのは良い考えだと思ったようです)

ご覧のとおり、他の人が示唆しているように、この問題を回避するためにIsCancellationRequestをチェックしたいと思っていましたが、タイムアウトでもtrueが返されます。

現在、設定されたタイムアウトが経過したかどうかを確認するためにタイマーを実装することを考えています。 しかし、明らかに私は死にたいですこれは恐ろしい回避策です。

こんにちは@FelipeTaveira

キャンセルはTimeoutHandlerの上流で、 HttpClient自体によって処理されるため、このアプローチは機能しません。 ハンドラーは、 Timeoutが発生したときにHttpClientによってキャンセルされるキャンセルトークンを受け取ります。 したがって、キャンセルトークンがキャンセルされていないことを確認することは、 HttpClientによって呼び出されるコードではなく、 HttpClientを呼び出すコードでのみ機能します。

カスタムハンドラーで機能させる方法は、 HttpClientTimeoutTimeout.InfiniteTimeSpanに設定し、独自のハンドラーでタイムアウト動作を制御することにより、無効にすることです。この記事に示されています(完全なコードはこちら

@thomaslevesqueああ、ありがとう! 私はあなたのコードを読んで、その部分はリクエストごとのタイムアウト構成をサポートすることであり、 TimeoutExceptionのものもサポートする必要があることに気づいていませんでした。

これが将来的に対処されることを願っています。

また、タイムアウト時にTaskCanceledExceptionがスローされると、混乱を招く可能性があります。

TaskCanceledExceptionsが、前の仕事で制作の問題に対処する際の主な混乱の原因であったことを証明できます。 通常、HttpClientへの呼び出しをラップし、必要に応じてTimeoutExceptionsをスローします。

@eiriktsarpalisありがとう。 5.0で対処する予定なので、必要に応じてその時間枠で取り上げることができます。これで、チームに参加できました😇。

1年? 😵.NETCoreの約束された敏捷性はどこにありますか?

1つの問題に基づいてプロジェクト全体の敏捷性を判断するのは少し...不公平です。
はい、少し恥ずかしいですが、まだこの問題がありますが、修正するのは難しく(互換性のある影響で)、最初に取り組むべきより高い優先度のビジネス目標がありました(ネットワーキングで)-歴史を通してマイルストーンの変化を見てくださいこの問題の。

ところで:他の多くのOSSプロジェクトと同様に、SLAやETAはなく、問題がいつ修正されるかについての約束もありません。 すべては、優先順位、他の作業、難易度、および問題に取り組むことができる専門家の数に依存します。

1つの問題に基づいてプロジェクト全体の敏捷性を判断するのは少し...不公平です。

まあ、それは正直な質問でした。 もう2年経ちましたが、もう1年かかるのを見て少し驚きました。 不快感はありませんが🙂。 この問題を解決するために、最初に答えなければならない/しなければならないいくつかの質問があったことを私は知っています。

妥協の世界では...私はそれが好きではありませんが、ここに噛むための別のオプションがあります:

TaskCanceledExceptionを投げ続けることもできますが、そのCancellationTokenを歩哨の値に設定します。

c# catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken) { }

そしてもう1つ: TaskCanceledExceptionから派生した新しいHttpTimeoutExceptionをスローします。

そしてもう1つ: TaskCanceledExceptionから派生した新しいHttpTimeoutExceptionをスローします。

私はそれがすべての懸念に対処すると思います。 これにより、タイムアウトを具体的にキャッチすることが容易になり、それでもTaskCanceledExceptionであるため、現在その例外をキャッチしているコードは、新しい例外もキャッチします(重大な変更はありません)。

そしてもう1つ:TaskCanceledExceptionから派生する新しいHttpTimeoutExceptionをスローします。

かなり醜いようですが、おそらく良い妥協点です。

そしてもう1つ: TaskCanceledExceptionから派生した新しいHttpTimeoutExceptionをスローします。

私はそれがすべての懸念に対処すると思います。 これにより、タイムアウトを具体的にキャッチすることが容易になり、それでもTaskCanceledExceptionであるため、現在その例外をキャッチしているコードは、新しい例外もキャッチします(重大な変更はありません)。

さて、次のようなコードを書いている人がいる可能性があります。

try
{
  // ...
}
catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException))
{
}

したがって、これは技術的には重大な変更になります。

また、私はそれが両方の世界の中で少し最悪だと思います。 一般的なTimeoutExceptionを同時に取得することはできませんが、重大な変更があります。

このようなコードを書いている人がいる可能性があるので、これは技術的には重大な変更になります

FWIW、ほとんどすべての変更が壊れている可能性があるので、何かを前進させるには、どこかに線を引く必要があります。 corefxでは、より派生した型を返したりスローしたりすることは壊れているとは考えていません。
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md#exceptions

妥協の世界では...私はそれが好きではありませんが、ここに噛むための別のオプションがあります:

TaskCanceledExceptionを投げ続けることもできますが、そのCancellationTokenを歩哨の値に設定します。

catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken)
{
}

私はこれが本当に好きです。 「醜い」以外に不利な点はありますか? タイムアウトが発生した今日のex.CancellationTokenとは何ですか?

「醜い」以外に不利な点はありますか?

これは他の場所で使用されている設計であるため、TimeoutTokenを渡して、タイムアウトが発生したときにキャンセルを通知するために使用できると考えるのは非常に簡単です。

私はこれが本当に好きです。 「醜い」以外に不利な点はありますか? タイムアウトが発生した今日のex.CancellationTokenとは何ですか?

ドキュメントで明示的に言及されている特定の例外タイプをキャッチするよりも、直感的ではありません。

私はOperationCanceledExceptionと派生型を考慮して、アプリケーションエラーではなく、実行の中断を示しています。 HttpClientがタイムアウト時にスローする例外は通常エラーです-HTTP APIの呼び出しがあり、それは利用できませんでした。 この動作は、ASP.NET Coreパイプラインミドルウェアなどのより高いレベルで例外を処理するときに複雑になる可能性があります。これは、このコードがOperationCanceledExceptionの2つのフレーバー、エラーと非エラーがあることを認識している必要があるためです。または、すべてのHttpClient呼び出しをthrow-catchブロックでラップするように強制します。

そしてもう1つ:TaskCanceledExceptionから派生する新しいHttpTimeoutExceptionをスローします。

かなり醜いようですが、おそらく良い妥協点です。

関係する設計上の制約と下位互換性の考慮事項を考えると、それは本当に醜いですか? このスレッドの誰かが、欠点がゼロの明らかに優れたソリューションを提案したことを私は知りません(使用法やデザインの純度など)

例外の種類を変更するだけでなく、これについて詳しく見ていきたいと思います。 「どこでタイムアウトしたのか」という長年の問題もあります。 -たとえば、要求がワイヤにぶつかることなくタイムアウトする可能性があります-これを確認する必要があり、ソリューションはそこに向かう方向と互換性がある必要があります。

例外の種類を変更するだけでなく、これについて詳しく見ていきたいと思います。 「どこでタイムアウトしたのか」という長年の問題もあります。 -たとえば、要求がワイヤにぶつかることなくタイムアウトする可能性があります-これを確認する必要があり、ソリューションはそこに向かう方向と互換性がある必要があります。

これは本当にあなたが知る必要があることですか? つまり、タイムアウトした場所に応じて、エラーの処理方法を変えますか? 私にとっては、タイムアウトが発生したことを知っているだけで通常は十分です。
もっとやりたいかどうかは気にしない。追加の要件があるため、この変更で.NET5トレインを見逃したくないだけだ。:wink:

@scalablecoryが提起した問題は注目に値すると思いますが、これがより早く修正されるのを妨げてはならないことに同意します。 タイムアウトの理由を評価できるようにするメカニズムを決定できますが、そのための例外の問題の修正を遅らせないでください...後でいつでもReasonプロパティを例外に追加できます。

@thomaslevesqueに同意します。 それを大きな問題として聞いたことはありません。 それはどこから来たのですか?

これはかなりニッチなデバッグのケースであり、少し明示的なアクションが必要な理由を掘り下げても問題ありませんが、一般的な開発者にとっては、1つのことを処理することだけを知っている/気にする必要があります。

例外の種類を変更するだけでなく、これについて詳しく見ていきたいと思います。 「どこでタイムアウトしたのか」という長年の問題もあります。 -たとえば、要求がワイヤにぶつかることなくタイムアウトする可能性があります-これを確認する必要があり、ソリューションはそこに向かう方向と互換性がある必要があります。

ここでの意味は、HttpClientが問題を修正するのではなく、適切なタイムアウト例外タイプをスローするのは各HttpMessageHandlerの責任であると推測します。

ここでの意味は、HttpClientが問題を修正するのではなく、適切なタイムアウト例外タイプをスローするのは各HttpMessageHandlerの責任であると推測します。

今日定義されている抽象化でそれがどのように可能かわかりません。 HttpMessageHandlerは、HttpClientのタイムアウトを認識していません。 HttpMessageHandlerに関する限り、キャンセルトークンが渡されるだけです。キャンセルトークンには、呼び出し元のキャンセルトークンの通知、HttpClientのCancelPendingRequestsの呼び出し、HttpClientの強制タイムアウトの期限切れなど、さまざまな理由でキャンセルが要求される可能性があります。

これは本当にあなたが知る必要があることですか? つまり、タイムアウトした場所に応じて、エラーの処理方法を変えますか? 私にとっては、タイムアウトが発生したことを知っているだけで通常は十分です。

サーバーがリクエストを処理した可能性があるかどうかを知ることは有用です。 また、診断にも最適です。リモートサーバーはタイムアウトしませんが、クライアントがタイムアウトを報告するのは苛立たしい経験です。

@davidshにはもう少し洞察があるかもしれません。

@thomaslevesqueに同意します。 それを大きな問題として聞いたことはありません。 それはどこから来たのですか?

これは、最近のNCL会議で提起された点でした。 これは、物事を明らかにしたり遅らせたりするのに十分重要ではないと判断したかもしれませんが、最初に簡単に説明する価値があります。

@ dotnet / ncl @ stephentoubこれを寝かせましょう。 私たちは、私たちが行うことはすべて微妙に壊れることを知っています。 これは最悪の選択肢のようです。 私は前進することを提案します:

TaskCanceledExceptionから派生した新しいHttpTimeoutExceptionをスローします。

異議はありますか?

TaskCanceledExceptionから派生する新しいHttpTimeoutExceptionをスローします。

これを実際にどのように行うのですか? これはおそらく、コードをスクラブして、 CancellationToken.ThrowIfCancellationRequestedを呼び出すすべての場所を見つけて、代わりに次のように変換する必要があることを意味します。

c# if (token.IsCancellationRequested) throw new HttpTimeoutException(EXTRASTUFF, token);

明示的な場所だけでなく、実際にはOperationCancelledExceptionと呼んでいる可能性もあります。

とにかく関連するすべてのコードを調べる必要のないオプションはありますか?

とにかく関連するすべてのコードを調べる必要のないオプションはありますか?

おそらくそうではありませんが、制御できない場所がある可能性があります(まだよくわかりません)。そのため、ランダムなTaskCancelledException / OperationCancelledExceptionを取得して、新しいHttpTimeoutExceptionを再スローする必要があります。

これはおそらく、コードをスクラブして、CancellationToken.ThrowIfCancellationRequestedを呼び出すすべての場所を見つけて、代わりに次のように変換する必要があることを意味します。

とにかく関連するすべてのコードを調べる必要のないオプションはありますか?

ではない正確に。

ハンドラーに関する限り、受け取るのはCancellationTokenだけです。ハンドラーは、そのトークンがどこから来たのか、誰が作成したのか、なぜキャンセルが要求されたのかを知りません。 彼らが知っているのは、ある時点でキャンセルが要求される可能性があることだけであり、それに応じてキャンセル例外をスローします。 したがって、ここではハンドラーで何もする必要はありません。

関連するCancellationTokenSourceを作成し、それにタイムアウトを設定しているのはHttpClient自体です。
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L488
タイムアウトは完全にHttpClient自体の発明であり、特別なものとして出現したキャンセル例外を処理するかどうかを知っているのはそれだけです。

つまり、上記の参照コードは、タイムアウトを個別に追跡するためにリファクタリングする必要があります。個別のCancellationTokenSourceとして、またはタイムアウトロジックを独自に処理し、CTSをキャンセルするだけでなく、フラグを設定することもできます。次に、チェックします。 ここで追加の割り当てを追加するという点で、これは有料ではない可能性がありますが、コードの観点からは比較的簡単です。

次に、次のようなHttpClientの場所で:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L536 -L541

https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L562 -L566
例外をキャッチすると、特別な場合のキャンセルが必要になり、タイムアウトが期限切れになっているかどうかを確認し、期限切れになっている場合は、それがキャンセルの原因であると想定して、目的のタイプの新しい例外をスローします。

タイムアウトが期限切れになっているかどうかを確認するために、特別な場合のキャンセルが必要になります。期限が切れている場合は、それがキャンセルの原因であると想定し、目的のタイプの新しい例外をスローします。

HttpClient.Timeoutプロパティが含まれていない場合、これはどのように機能しますか? おそらくそれは「無限」に設定されています。 HttpClient API(GetAsync、SendAyncなど)に直接渡されるキャンセルトークンはどうですか? 私はそれが同じように機能すると思いますか?

HttpClient.Timeoutプロパティが含まれていない場合、これはどのように機能しますか?

質問がわかりません...ここで話しているタイムアウトはこれだけです。 Infiniteに設定されている場合、このタイムアウトによってキャンセルが発生することはなく、何もする必要はありません。 すでに特殊なケースのTimeout == Infiniteであり、次のことを続けます。
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L481

HttpClient API(GetAsync、SendAyncなど)に直接渡されるキャンセルトークンはどうですか? 私はそれが同じように機能すると思いますか?

渡されたトークンはすべて、独自の作成の1つと組み合わせます。
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L485
以前のコメントで述べたように、HttpClient.Timeoutの独自の処理を追跡します。 発信者がタイムアウトで構成したトークンを渡すことを選択した場合、それを特別に扱うのは彼ら次第です。私たちに関する限り、それは単なるキャンセル要求です。

異議はありますか?

たとえば、TimeoutExceptionを内部例外としてラップするキャンセル例外をスローするのではなく、既存のTimeoutExceptionと「競合する」新しい派生例外タイプを導入するのはなぜですか。 キャンセルがタイムアウトによるものかどうかを消費者がプログラムで判断できるようにすることが目標である場合、それで十分でしょうか? 新しい表面積を必要とせずに、同じ情報を伝達しているように見えます。

たとえば、TimeoutExceptionを内部例外としてラップするキャンセル例外をスローするのではなく、既存のTimeoutExceptionと「競合する」新しい派生例外タイプを導入する理由

便宜上、主に。 書くのがより簡単で直感的です

catch(HttpTimeoutException)

よりも

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

そうは言っても、タイムアウトを特定するのが合理的に簡単である限り、どちらの方法でも問題ありません。

たとえば、TimeoutExceptionを内部例外としてラップするキャンセル例外をスローしますか?

それは良い妥協案だと思います。 これは、これらの種類のタイムアウトを処理する際の私の主な迷惑の原因である例外ログを明確にするのに役立ちます。 また、基になるハンドラーによってタイムアウトが発生するのではなく、これがHttpClientが要求をキャンセルしているという事実をより適切に伝えることもできます。 また、重大な変更はありません。

トリアージ:

  • このシナリオを更新して、 InnerExceptionTimeoutExceptionに、タイムアウトのトリガーによって引き起こされたキャンセルを簡単に区別できるロギング/診断用のメッセージを追加します。
  • キャンセルを区別するためにプログラムで検査する方法についてのガイダンスでドキュメントを更新します: TimeoutExceptionをチェックし、無限のTimeoutを使用し、タイムアウト期間でCancellationTokenを渡すなど。

ここでの解決にはある程度の妥協が伴うことを認識しており、これにより既存のコードとの良好なレベルの互換性が提供されると考えています。

たとえば、TimeoutExceptionを内部例外としてラップするキャンセル例外をスローするのではなく、既存のTimeoutExceptionと「競合する」新しい派生例外タイプを導入する理由

便宜上、主に。 書くのがより簡単で直感的です

catch(HttpTimeoutException)

よりも

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

そうは言っても、タイムアウトを特定するのが合理的に簡単である限り、どちらの方法でも問題ありません。

@scalablecory数時間で議論を見逃しているように見えたのは残念です。この種のことを一般的なLoB.NET開発者と伝えた経験では、派生したHttpTimeoutExceptionをキャッチする方が、理解/採用するよりもはるかに簡単です。内部例外で例外フィルターを使用することを忘れないでください。 誰もが例外フィルターが何であるかさえ知っているわけではありません。

同時に、あなたが言ったように、これは明確な勝利/妥協のない解決策がないシナリオの1つであることを完全に認識しています。

その点については@ericsampsonに同意しましたが、それは多くの既存のユーザーにとって大きな変化となるでしょう。 @scalablecoryが述べたように、互換性の損傷を制限しながら改善を行うために妥協しました。

例外について考えるとき、一般的に、私はラッピングアプローチの方が良いと思います。 OperationCanceledExceptionから例外を取得すると、そのような例外は非同期使用でのみ使用可能になります。 そしてそれでさえ仕事にきついです。 過去に見たように、非同期アプローチにはいくつかの進化があり、タスクを実装の詳細と見なしますが、たとえば、TimeoutExceptionは変更されないソケットの概念の一部です。

その点については@ericsampsonに同意しましたが、それは多くの既存のユーザーにとって大きな変化となるでしょう。 @scalablecoryが述べたように、互換性の損傷を制限しながら改善を行うために妥協しました。

私は必ずしもラッピングオプションに同意しませんが、私が理解している限り、問題は派生オプションが重大な変更であるということではなく、ラッピングオプションがより単純であるということを指摘する価値があると思います。 議論の前半で@stephentoubが述べたように、派生例外をスローすることは、corefxガイドラインによる重大な変更とは見なされません。

TaskCanceledExceptionからHttpTimeoutExceptionを派生させると、これら2つの例外間の「isa」関係に違反します。 特にHttpTimeoutExceptionを処理しない限り、呼び出し元のコードはキャンセルが発生したと見なします。 これは基本的に、キャンセルトークンがキャンセルされているかどうかをチェックして、タイムアウトかどうかを判断するときに実行する必要がある処理と同じです。 さらに、コアライブラリに2つのタイムアウト例外があると混乱します。
TaskCanceledExceptionの内部例外としてTimeoutExceptionを追加しても、キャンセルトークンを確認してタイムアウトかどうかを判断できるため、ほとんどの場合、追加情報は提供されません。

唯一の賢明な解決策は、タイムアウト時にスローされる例外をTimeoutExceptionに変更することだと思います。 はい、それは重大な変更になりますが、問題はすでにいくつかのメジャーリリースにまたがっており、重大な変更はメジャーリリースの目的です。

タイムアウトが発生したときにメソッドがTaskCanceledExceptionおよび/またはOperationCanceledException (両方の具象タイプが可能であるように見える)をスローする可能性があることを示すために、既存のバージョンのドキュメントを更新できますか? 現在、ドキュメントにはHttpRequestExceptionがタイムアウトを扱っていることが示されていますが、これは正しくなく誤解を招く恐れがあります。

@qidydlが計画です...タイムアウトが発生する可能性のある方法と、タイムアウトを詳細に説明する方法を完全に文書化します。

@ alnikola 、@ scalablecory、 @ davidsh、@ karelz そしてこのディスカッション/実装に携わった他のすべての人に感謝します。本当に感謝しています。

このページは役に立ちましたか?
0 / 5 - 0 評価