C# 编写重试逻辑的最简洁方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1563191/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cleanest way to write retry logic?
提问by noctonura
Occasionally I have a need to retry an operation several times before giving up. My code is like:
有时我需要在放弃之前重试几次操作。我的代码是这样的:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
I would like to rewrite this in a general retry function like:
我想在一个通用的重试函数中重写它,例如:
TryThreeTimes(DoSomething);
Is it possible in C#? What would be the code for the TryThreeTimes()
method?
在 C# 中可能吗?该TryThreeTimes()
方法的代码是什么?
采纳答案by LBushkin
Blanket catch statements that simply retry the same call can be dangerous if used as a general exception handling mechanism. Having said that, here's a lambda-based retry wrapper that you can use with any method. I chose to factor the number of retries and the retry timeout out as parameters for a bit more flexibility:
如果用作一般异常处理机制,简单地重试相同调用的一揽子 catch 语句可能是危险的。话虽如此,这里有一个基于 lambda 的重试包装器,您可以将其用于任何方法。我选择将重试次数和重试超时作为参数考虑,以获得更大的灵活性:
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
You can now use this utility method to perform retry logic:
您现在可以使用此实用程序方法来执行重试逻辑:
Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
or:
或者:
Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));
or:
或者:
int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);
Or you could even make an async
overload.
或者你甚至可以做一个async
超载。
回答by Mark P Neyer
public delegate void ThingToTryDeletage();
public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime)
{
while(true)
{
try
{
ThingToTryDelegate();
} catch {
if( --N == 0) throw;
else Thread.Sleep(time);
}
}
回答by Drew Noakes
public void TryThreeTimes(Action action)
{
var tries = 3;
while (true) {
try {
action();
break; // success!
} catch {
if (--tries == 0)
throw;
Thread.Sleep(1000);
}
}
}
Then you would call:
然后你会打电话:
TryThreeTimes(DoSomething);
...or alternatively...
……或者……
TryThreeTimes(() => DoSomethingElse(withLocalVariable));
A more flexible option:
更灵活的选择:
public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
action();
break; // success!
} catch {
if (--tryCount == 0)
throw;
Thread.Sleep(sleepPeriod);
}
}
}
To be used as:
用作:
DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);
A more modern version with support for async/await:
支持 async/await 的更现代版本:
public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
await action();
return; // success!
} catch {
if (--tryCount == 0)
throw;
await Task.Delay(sleepPeriod);
}
}
}
To be used as:
用作:
await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
回答by csharptest.net
You might also consider adding the exception type you want to retry for. For instance is this a timeout exception you want to retry? A database exception?
您还可以考虑添加要重试的异常类型。例如,这是您要重试的超时异常吗?数据库异常?
RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);
public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
if (action == null)
throw new ArgumentNullException("action");
if (retryOnExceptionType == null)
throw new ArgumentNullException("retryOnExceptionType");
while (true)
{
try
{
action();
return;
}
catch(Exception e)
{
if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
throw;
if (retryTimeout > 0)
System.Threading.Thread.Sleep(retryTimeout);
}
}
}
You might also note that all of the other examples have a similar issue with testing for retries == 0 and either retry infinity or fail to raise exceptions when given a negative value. Also Sleep(-1000) will fail in the catch blocks above. Depends on how 'silly' you expect people to be but defensive programming never hurts.
您可能还注意到,所有其他示例在测试重试 == 0 时都有类似的问题,并且在给定负值时重试无穷大或无法引发异常。Sleep(-1000) 也会在上面的 catch 块中失败。取决于您期望人们有多“愚蠢”,但防御性编程永远不会受到伤害。
回答by mike
Or how about doing it a bit neater....
或者把它做得更整洁一点怎么样....
int retries = 3;
while (retries > 0)
{
if (DoSomething())
{
retries = 0;
}
else
{
retries--;
}
}
I believe throwing exceptions should generally be avoided as a mechanism unless your a passing them between boundaries (such as building a library other people can use). Why not just have the DoSomething()
command return true
if it was successful and false
otherwise?
我相信通常应该避免抛出异常作为一种机制,除非您在边界之间传递它们(例如构建其他人可以使用的库)。如果成功,为什么不直接DoSomething()
返回命令true
,false
否则返回?
EDIT:And this can be encapsulated inside a function like others have suggested as well. Only problem is if you are not writing the DoSomething()
function yourself
编辑:这也可以像其他人建议的那样封装在一个函数中。唯一的问题是如果您不是DoSomething()
自己编写函数
回答by Robert Rossney
I'd implement this:
我会实现这个:
public static bool Retry(int maxRetries, Func<bool, bool> method)
{
while (maxRetries > 0)
{
if (method(maxRetries == 1))
{
return true;
}
maxRetries--;
}
return false;
}
I wouldn't use exceptions the way they're used in the other examples. It seems to me that if we're expecting the possibility that a method won't succeed, its failure isn't an exception. So the method I'm calling should return true if it succeeded, and false if it failed.
我不会像在其他示例中那样使用异常。在我看来,如果我们期待一个方法不会成功的可能性,那么它的失败也不例外。所以我调用的方法应该在成功时返回 true,如果失败则返回 false。
Why is it a Func<bool, bool>
and not just a Func<bool>
? So that if I wanta method to be able to throw an exception on failure, I have a way of informing it that this is the last try.
为什么它是 aFunc<bool, bool>
而不仅仅是 a Func<bool>
?因此,如果我想要一个方法能够在失败时抛出异常,我有一种方法可以通知它这是最后一次尝试。
So I might use it with code like:
所以我可能会将它与以下代码一起使用:
Retry(5, delegate(bool lastIteration)
{
// do stuff
if (!succeeded && lastIteration)
{
throw new InvalidOperationException(...)
}
return succeeded;
});
or
或者
if (!Retry(5, delegate(bool lastIteration)
{
// do stuff
return succeeded;
}))
{
Console.WriteLine("Well, that didn't work.");
}
If passing a parameter that the method doesn't use proves to be awkward, it's trivial to implement an overload of Retry
that just takes a Func<bool>
as well.
如果传递一个方法不使用的参数被证明是笨拙的,那么实现一个Retry
只需要 a的重载是微不足道的Func<bool>
。
回答by Eric Lippert
This is possibly a bad idea. First, it is emblematic of the maxim "the definition of insanity is doing the same thing twice and expecting different results each time". Second, this coding pattern does not compose well with itself. For example:
这可能是个坏主意。首先,它象征着格言“精神错乱的定义是两次做同样的事情,每次都期待不同的结果”。其次,这种编码模式与自身的组合不好。例如:
Suppose your network hardware layer resends a packet three times on failure, waiting, say, a second between failures.
假设您的网络硬件层在失败时重新发送一个数据包三次,在两次失败之间等待一秒钟。
Now suppose the software layer resends an notification about a failure three times on packet failure.
现在假设软件层在数据包失败时重新发送有关失败的通知 3 次。
Now suppose the notification layer reactivates the notification three times on an notification delivery failure.
现在假设通知层在通知传递失败时重新激活通知 3 次。
Now suppose the error reporting layer reactivates the notification layer three times on a notification failure.
现在假设错误报告层在通知失败时重新激活通知层 3 次。
And now suppose the web server reactivates the error reporting three times on error failure.
现在假设 Web 服务器在错误失败时重新激活错误报告 3 次。
And now suppose the web client resends the request three times upon getting an error from the server.
现在假设 Web 客户端在从服务器收到错误时重新发送请求 3 次。
Now suppose the line on the network switch that is supposed to route the notification to the administrator is unplugged. When does the user of the web client finally get their error message? I make it at about twelve minutes later.
现在假设应该将通知路由到管理员的网络交换机上的线路已拔掉。Web 客户端的用户何时最终收到他们的错误消息?我在大约十二分钟后完成。
Lest you think this is just a silly example: we have seen this bug in customer code, though far, far worse than I've described here. In the particular customer code, the gap between the error condition happening and it finally being reported to the user was several weeksbecause so many layers were automatically retrying with waits. Just imagine what would happen if there were tenretries instead of three.
免得您认为这只是一个愚蠢的例子:我们已经在客户代码中看到了这个错误,尽管比我在这里描述的要糟糕得多。在特定的客户代码中,发生错误情况和最终报告给用户之间的间隔是几周,因为很多层都在等待自动重试。试想一下,如果重试次数为10 次而不是3 次会发生什么。
Usually the right thing to do with an error condition is report it immediately and let the user decide what to do.If the user wants to create a policy of automatic retries, let them create that policy at the appropriate level in the software abstraction.
通常,处理错误情况的正确做法是立即报告并让用户决定要做什么。如果用户想要创建自动重试策略,让他们在软件抽象的适当级别创建该策略。
回答by Brian
Allowing for functions and retry messages
允许函数和重试消息
public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
Guard.IsNotNull(method, "method");
T retval = default(T);
do
{
try
{
retval = method();
return retval;
}
catch
{
onFailureAction();
if (numRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(retryTimeout);
}
} while (numRetries-- > 0);
return retval;
}
回答by Igor Pashchuk
Building on the previous work, I thought about enhancing the retry logic in three ways:
在之前的工作的基础上,我想到了三个方面来增强重试逻辑:
- Specifying what exception type to catch/retry. This is the primary enhacement as retrying for any exception is just plain wrong.
- Not nesting the last try in a try/catch, achieving slightly better performance
Making it an
Action
extension methodstatic class ActionExtensions { public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception { if (action == null) throw new ArgumentNullException("action"); while( retries-- > 0 ) { try { action( ); return; } catch (T) { Thread.Sleep( retryDelay ); } } action( ); } }
- 指定要捕获/重试的异常类型。这是主要的增强,因为重试任何异常都是完全错误的。
- 不在 try/catch 中嵌套最后一次尝试,从而获得稍微更好的性能
使其成为
Action
扩展方法static class ActionExtensions { public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception { if (action == null) throw new ArgumentNullException("action"); while( retries-- > 0 ) { try { action( ); return; } catch (T) { Thread.Sleep( retryDelay ); } } action( ); } }
The method can then be invoked like so (anonymous methods can be used as well, of course):
然后可以像这样调用该方法(当然也可以使用匿名方法):
new Action( AMethodThatMightThrowIntermittentException )
.InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
回答by Paolo Sanchi
I had the need to pass some parameter to my method to retry, and have a result value; so i need an expression.. I build up this class that does the work (it is inspired to the the LBushkin's one) you can use it like this:
我需要将一些参数传递给我的方法来重试,并有一个结果值;所以我需要一个表达式..我建立了这个完成工作的类(它受到 LBushkin 的启发)你可以像这样使用它:
static void Main(string[] args)
{
// one shot
var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
// delayed execute
var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
var res2 = retry.Execute();
}
static void fix()
{
Console.WriteLine("oh, no! Fix and retry!!!");
}
static string retryThis(string tryThis)
{
Console.WriteLine("Let's try!!!");
throw new Exception(tryThis);
}
public class Retry<TResult>
{
Expression<Func<TResult>> _Method;
int _NumRetries;
TimeSpan _RetryTimeout;
Action _OnFailureAction;
public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
_Method = method;
_NumRetries = numRetries;
_OnFailureAction = onFailureAction;
_RetryTimeout = retryTimeout;
}
public TResult Execute()
{
TResult result = default(TResult);
while (_NumRetries > 0)
{
try
{
result = _Method.Compile()();
break;
}
catch
{
_OnFailureAction();
_NumRetries--;
if (_NumRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(_RetryTimeout);
}
}
return result;
}
public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
return retry.Execute();
}
}
ps. the LBushkin's solution does one more retry =D
附:LBushkin 的解决方案再重试一次 =D