_HttpClient.PostAsync
_に再試行機能を追加するための基本的な拡張メソッドを作成しました:
_public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
}
_
上記のコードでは、次のエラーが表示されます。
エラーCS0161 'HttpClientExtensions.PostWithRetryAsync(HttpClient、Uri、HttpContent、int、Action)':すべてのコードパスが値を返すわけではありません。
最後にthrow new InvalidOperationException()
(またはその点で_return null
_)を追加すると、エラーは期待どおりになくなります。私が本当に知りたいのは、値が返されたり例外がスローされたりすることなく、実際にこのメソッドを終了するコードパスはありますか?見えません。この場合、コンパイラ以上のことを知っていますか、それとも逆ですか?
簡単な理由は、すべての実行フローパスがreturnステートメント(または例外)で終わることをコンパイラーが静的に検証できる必要があるためです。
あなたのコードを見てみましょう、それは含まれています:
while
ループを制御するいくつかの変数while
ステートメントが埋め込まれたreturn
ループreturn
ステートメントの後したがって、基本的にコンパイラはこれらのことを確認する必要があります。
while
ループが実際に実行されることreturn
ステートメントがalways実行されることコンパイラはこれを単に確認することができません。
非常に簡単な例を試してみましょう。
public int Test()
{
int a = 1;
while (a > 0)
return 10;
}
この簡単な例では、まったく同じエラーが生成されます。
CS0161 'Test()':すべてのコードパスが値を返すわけではありません
したがって、コンパイラはこれらの事実のためにそれを推測することはできません。
a
はローカル変数です(ローカルコードのみが影響を与えることができることを意味します)a
の初期値は1
、および変更されることはありませんa
変数がゼロ(つまり)より大きい場合、return
ステートメントに到達します。コードは常に値10を返します。
次の例を見てください。
public int Test()
{
const int a = 1;
while (a > 0)
return 10;
}
唯一の違いは、a
をconst
にしたことです。コンパイルは完了しましたが、これはオプティマイザーがループ全体を削除できるようになったためです。最終的なILは次のようになります。
Test:
IL_0000: ldc.i4.s 0A
IL_0002: ret
while
ループ全体とローカル変数はなくなり、残りはすべてこれだけです。
return 10;
そのため、コンパイラーはこれらのことを静的に分析するときに変数値を見ないことは明らかです。この機能を実装して正しく機能させるためのコストは、おそらくそれを行わないことの効果またはマイナス面を上回ります。 「すべての機能は穴から100ポイントで始まります。つまり、言語全体に組み込むにはパッケージ全体に大きな正の効果をもたらす必要があります。」 。
はい、これは間違いなく、コンパイラよりもコードについて多くを知っている場合です。
完全を期すために、コードが流れるすべての方法を見てみましょう。
maxAttempts
が1未満の場合、例外を発生して早期に終了できますwhile
が1でattempt
が少なくとも1であるため、ItwillはmaxAttempts
- loopに入ります。try
ステートメント内のコードがHttpRequestException
をスローする場合、attempt
がインクリメントされ、maxAttempts
以下の場合はwhile
-loopは別の繰り返しを行います。現在maxAttempts
よりも大きい場合、例外が発生します。そのため、基本的に、このコードは常に例外をスローするかリターンするかのどちらかと言えますが、コンパイラはこれを静的に検証することはできません。
エスケープハッチ(attempt > maxAttempts
)while
- loopの基準として、さらにcatch
ブロックの内側の2つの場所で、while
から削除するだけでコードを簡素化できます。 -ループ:
while (true)
{
...
if (attempt > maxAttempts)
throw;
...
}
while
- loopを少なくとも1回実行することが保証されており、実際にそれがcatch
ブロックで終了するので、それを形式化するだけで、コンパイラは再び幸せになります。
これで、フロー制御は次のようになります。
while
ループはalwaysを実行します(または既に例外をスローしています)while
ループはnever終了します(内部にbreak
がないため、ループの後にコードは不要です)ループを抜ける唯一の可能な方法は、明示的なreturn
または例外のいずれかです。この特定のエラーメッセージの焦点は、潜在的に明示的なreturn
なしでメソッドをエスケープします。誤ってメソッドをエスケープする方法はないため、残りのチェックは単純にスキップできます。
このメソッドでもコンパイルされます:
public int Test()
{
while (true)
{
}
}
HttpRequestExceptionがスローされ、catchブロックが実行されると、条件に応じてthrowステートメントがスキップされ(試行> maxAttempts)、パスが何も返さないようになります。
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
else
return something; // HERE YOU NEED TO RETURN SOMETHING
}
}
}
ただし、ループを継続する場合は、最後に戻る必要があります。
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
return something; // HERE YOU NEED TO RETURN SOMETHING
}
As Errorはnot all code paths return a value
各コードパスの値を返していません
例外または戻り値をスローする必要があります
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
else
return null;//you must return something for this code path
}
すべてのコードパスが値を返すようにコードを変更できます。コードはこのようなものでなければなりません
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
HttpResponseMessage response = null;
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
return response;
}