问答中心分类: C#写重试逻辑的最干净方法?
0
匿名用户 提问 1小时 前

有时我需要在放弃之前重试一个操作几次。我的代码如下:

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

我想在常规重试函数中重写此函数,如:

TryThreeTimes(DoSomething);

是否可以使用C#?代码是什么TryThreeTimes()方法

29 Answers
0
Michael Wolfenden 回答 1小时 前

你应该试试波莉. 这是一个。NET库,允许开发人员以流畅的方式表达瞬时异常处理策略,如重试、永远重试、等待重试或断路器。
实例

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());
user6395764 回复 1小时 前

OnRetry委托实际上是什么?我假设这是发生异常时我们需要执行的操作。所以,当在重试时发生异常时,委托将调用并随后执行委托。是这样吗?

Sina Riani 回复 1小时 前

我应该在哪里使用此代码段?如果答案是启动。cs,如何注册策略?

D.R. 回复 1小时 前

Q: OnRetry委托实际上是什么?A: 它只允许您在执行重试时执行某些操作(例如,记录某些内容)。您不需要在那里调用Execute,这是自动发生的。

Keith Banner 回复 1小时 前

@SinaRiani你可以对Polly做这样的事。堆栈溢出。电话:68013076/4267686

0
Drew Noakes 回答 1小时 前
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

然后你会打电话:

TryThreeTimes(DoSomething);

…或者,或者。。。

TryThreeTimes(() => DoSomethingElse(withLocalVariable));

更灵活的选项:

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);
        }
   }
}

用作:

DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);

更现代的版本,支持异步/等待:

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);
        }
   }
}

用作:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
Stefanvds 回复 1小时 前

最好将if更改为:--retryCount <= 0因为如果您想通过将其设置为0来禁用重试,则此操作将永远持续。从技术上来说retryCount不是很好的名称,因为如果将其设置为1,则不会重试。或者将其重命名为tryCount或者把……放在后面。

Drew Noakes 回复 1小时 前

@赛勒我同意。但是OP(和所有其他答案)使用Thread.Sleep. 替代方法是使用计时器,或者现在更可能使用async要重试,请使用Task.Delay.

Drew Noakes 回复 1小时 前

我添加了一个异步版本。

Kiquenet 回复 1小时 前

只有打破如果操作returns true?Func<bool>

Drew Noakes 回复 1小时 前

@ibda如果您想释放线程来做其他工作,而不是睡觉,那么您只会使用异步版本。例如,如果在线程池上运行work,就不应该让这些线程处于睡眠状态。

0
Eric Lippert 回答 1小时 前

这可能是个坏主意。首先,这是一句格言的象征:“精神错乱的定义是两次做同样的事情,每次都期望不同的结果”。其次,这种编码模式本身并不能很好地组合。例如:
假设您的网络硬件层在出现故障时重新发送一个数据包三次,例如,在两次故障之间等待一秒钟。
现在,假设软件层在数据包失败时三次重新发送关于失败的通知。
现在,假设通知层在通知传递失败时重新激活通知三次。
现在,假设错误报告层在通知失败时重新激活通知层三次。
现在假设web服务器在错误失败时重新激活错误报告三次。
现在,假设web客户端在从服务器收到错误后重新发送请求三次。
现在,假设网络交换机上用于将通知路由到管理员的线路已拔出。web客户端的用户何时最终收到错误消息?我大约十二分钟后到达。
以免您认为这只是一个愚蠢的例子:我们在客户代码中看到了这个bug,尽管比我在这里描述的要糟糕得多。在特定的客户代码中,错误条件发生与最终报告给用户之间的差距是因为很多层都在等待中自动重试。想象一下如果有重试,而不是.
通常正确处理错误条件的方法是立即报告,让用户决定要做什么。如果用户想要创建自动重试的策略,让他们在软件抽象的适当级别创建该策略。

SolutionYogi 回复 1小时 前

+1、Raymond在这里分享了一个真实的例子,博客。msdn。com/oldnewthing/archive/2005/11/07/489807。aspx

nohat 回复 1小时 前

-1对于自动批处理系统遇到的瞬时网络故障,此建议无效。

Jim L 回复 1小时 前

不确定这是否表示“不要做”,然后是“做”。问这个问题的大多数人可能是从事软件抽象的人。

Erik Funkenbusch 回复 1小时 前

当您有使用网络资源(如web服务)的长时间运行的批处理作业时,您不能期望网络100%可靠。在使用过程中,偶尔会出现超时、套接字断开,甚至可能出现虚假的路由故障或服务器中断。一种选择是失败,但这可能意味着以后重新启动一个冗长的作业。另一种选择是在适当延迟的情况下重试几次,看看这是否是暂时的问题,然后失败。我同意你的作文,你必须知道。。但有时这是最好的选择。

Jens 回复 1小时 前

正如@Mystere-Man所说,在某些情况下,用户不一定会立即被重试对话框击中。但如果必须这样做,这里的情况很糟糕,所以永远不要使用“重试X次”。。。如果必须自动重试,请使用“在Y时间内每X重试一次”,例如“在500毫秒内每100毫秒重试一次”。。。然后,您可以确保每个层的时间不会增长,因为顶层永远不会重试,因为底层重试会超过最长时间。

Michael Richardson 回复 1小时 前

我认为你在回答的开头引用的话很有意思。如果之前的经验经常给你相同的结果,“期待不同的结果”只是一种疯狂。虽然软件是建立在一致性承诺的基础上的,但在某些情况下,我们肯定需要与我们无法控制的不可靠力量进行交互。

Eric Lippert 回复 1小时 前

@probackpacker:当然——那么问题是“不可预测的力量在多大的时间尺度上可能会发生变化?”电源尖峰会使网络路由器在一毫秒内无响应,而有人会关闭路由器一小时,这两件事截然不同!(批评我引用的另一种方式当然是什么都可以模糊地就像精神错乱的实际定义一样。)

Eric Lippert 回复 1小时 前

@probackpacker:举个例子:我使用“再试一次”的方法是在我编写文件时,关闭它,然后不久再打开它。(想想一个日志文件,它用来诊断一个无法预测的程序崩溃;我不知道最后一次关闭是什么时候,我不想丢失任何数据。)它是非常常见于编写错误的病毒检查器锁定文件片刻它被关闭,然后花几毫秒检查病毒;如果我尝试在这几毫秒内打开文件,它将失败,但第二次尝试可能会成功。

Michael Richardson 回复 1小时 前

@埃里克·利珀特:我非常同意,我仍然认为你的答案很有价值。感谢您对以下情况的澄清:使用重试逻辑。我目前正在使用一些C#代码,这些代码使用异步/等待重复写入数据库。有时一切都很顺利,有时失败得很惨,原因不明。有鉴于此,我读了你的答案,忍不住思考了这句话背后的假设。

CZahrobsky 回复 1小时 前

实际上我认为这是非确定性的定义。

JackCid 回复 1小时 前

通常,当您有一个连接到数据库或从web读取数据的自动化流程时,可能会出现问题,但只需添加重试逻辑即可解决。我想说,对于这些情况,必须实现重试逻辑。

Eric Lippert 回复 1小时 前

@JuanAndrésCidMolina:当您有一个连接到网络资源的自动化进程时,事情可能会出错,只需添加重试逻辑,情况就会变得更糟。我想说的是,在这些情况下,必须不实现重试逻辑。我更感兴趣的是避免对用户造成伤害,而不是避免带来不便。

JackCid 回复 1小时 前

@埃里克·利珀特是真的。这实际上取决于你在做什么。

BartoszKP 回复 1小时 前

糟糕的建议。例如,指数退避模式是众所周知的,并且通常用于大型分布式系统。以下是引用此模式的众多来源之一:文档。微软com/en-us/dotnet/architecture/microservices/…正确的答案应该引导OP朝着这个模式前进,而不是简单地说“不要这样做”。

Eric Lippert 回复 1小时 前

@巴托斯克普:我对你的评论有点困惑。指数后退重试如何防止这种模式与自身的组合导致的长时间延迟?在我看来,这会使情况变得更糟,而不是更好。指数退避减轻了自己服务的不必要剂量;据我所知,这并不是缓解我在回答中提出的关于作文的问题。你能解释一下吗?

BartoszKP 回复 1小时 前

@埃里克·利珀特是的,我们在谈论不同的事情。然而,你对这个问题的回答对我来说是“不要这样做”。这是一个糟糕的建议,因为这样做有充分的理由,并且有常用的模式(因此我的例子)。因此,总的来说,暗示这是一个坏主意会误导IMHO,尤其是如果下面是一个非常具体的例子的大量“假设”——假设OP正在询问这类错误(很可能不是)。而且,您引用的准则在确实发生瞬时错误的系统中是不相关的。

Eric Lippert 回复 1小时 前

@巴托斯克普:我接受你的观点。然而指出“格言并不适用于所有不适用的情况”并不是一种可采取行动的批评。“X不适用于它不适用的情况”是一个同义反复。

BartoszKP 回复 1小时 前

@EricLippert你从这个格言开始,作为一个独立的论点(你说“第一”,整个具体的例子附在你以“第二”开始的部分)。我相信你不是故意的,但这第一部分感觉就像是在对一个愚蠢到连做两次都想得到不同结果的人说话:-)抱歉,如果这感觉像是吹毛求疵,我真诚地认为,虽然你答案的第二部分是一个有价值的例子,但第一部分是从读者的错误开始的。

Eric Lippert 回复 1小时 前

@BartoszKP:你从一个不相关的技术评论开始,你承认这与我在回答中提出的问题无关,现在你开始对语气进行评论。我只会对关于技术价值的实质性批评做出进一步回应,而不是对词语选择和你对我“语气”的解释。我鼓励你们将你们的评论局限于实质性的评论。

BartoszKP 回复 1小时 前

@EricLippert技术批判是相关的-你提出了一个具体的场景并声称(见你的第一点)它总体上否定了这个想法,这是从技术上讲不准确的我鼓励你不要跳过我评论中的技术部分,而要关注非技术性的建议。我想我没有什么要补充的了,所以我就到此为止——祝你白天/晚上愉快,谢谢你抽出时间。

0
Grigori Melnik 回答 1小时 前

这个瞬态故障处理应用程序块提供可扩展的重试策略集合,包括:

  • 增量
  • 固定时间间隔
  • 指数后退

它还包括一系列基于云的服务的错误检测策略。
有关更多信息,请参阅本章开发人员指南。
可通过努吉(搜索’黄玉‘).

Matthew Lock 回复 1小时 前

有趣的你能在Windows Azure之外使用它吗,比如在Winforms应用程序中?

Grigori Melnik 回复 1小时 前

绝对地使用核心重试机制并提供您自己的检测策略。我们有意将其解耦。在此处找到核心nuget包:努吉。组织/包/TransientFaultHandling。果心

Grigori Melnik 回复 1小时 前

此外,该项目现在处于Apache 2.0下,并接受社区捐款。又称作ms/entlibopen

Grigori Melnik 回复 1小时 前

@亚历克斯。它的碎片正在进入平台。

Ohad Schneider 回复 1小时 前

现在已经弃用了,我上次使用它时,它包含一些错误,据我所知,这些错误过去没有,将来也不会修复:github。com/MicrosoftArchive/….

0
Martin R-L 回答 1小时 前

我是递归和扩展方法的粉丝,下面是我的两分钱:

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}