【问题标题】:The best way (pattern) to execute something while exception is thown抛出异常时执行某事的最佳方式(模式)
【发布时间】:2012-12-20 15:47:50
【问题描述】:

下面的代码显示了尝试封装逻辑以在捕获异常时重新运行某些东西。

是否存在模式或其他东西可以做到这一点?或者您会建议对该代码进行哪些改进?

    public static void DoWhileFailing(int triesAmount, int pauseAmongTries, Action codeToTryRun) {
        bool passed = false;
        Exception lastException = null;

        for (int i = 0; !passed && i < triesAmount; i++) {
            try {
                if (i > 0) {
                    Thread.Sleep(pauseAmongTries);
                }
                codeToTryRun();
                passed = true;
            } catch(Exception e) {
                lastException = e;
            }
        }

        if (!passed && lastException != null) {
            throw new Exception(String.Format("Something failed more than {0} times. That is the last exception catched.", triesAmount), lastException);
        }
    }

【问题讨论】:

  • 如果你有passed = true,你可以只使用return,最后你不需要检查它,只需抛出一个异常。有些人真的讨厌多个出口点,有些人则不然。在这里,我认为多个退出点是有意义的。
  • The "catch(Exception e)" is wronge 你应该抓住你期望能够从中恢复的期望并检查它的细节。例如。数据库异常检查它是否由于死锁而失败。否则这段代码很像我们除了在遇到异常时清除连接缓存之外的代码,因为 SqlConnection 将重用断开的连接。
  • 重试你的意思是什么?
  • @IanRingrose,也许只是有一个通用参数来指定在重新运行之前要捕获哪个异常,可能会更好。类似于 juharr 的回答,尽管他建议将异常“传递”给调用者,调用者可以通过委托检查异常。
  • @Luciano,我发现的问题是数据库死锁和集群故障转移会抛出与“真实”数据库错误相同的异常类。因此需要处理异常消息文本和/或错误号。

标签: c# exception-handling for-loop try-catch


【解决方案1】:

我会重写这个来消除一些变量,但总的来说你的代码是可以的:

public static void DoWhileFailing(int triesAmount, int pauseAmongTries, Action codeToTryRun) {
    if (triesAmount<= 0) {
        throw new ArgumentException("triesAmount");
    }
    Exception ex = null;
    for (int i = 0; i < triesAmount; i++) {
        try {
            codeToTryRun();
            return;
        } catch(Exception e) {
            ex = e;
        }
        Thread.Sleep(pauseAmongTries);
    }
    throw new Exception(String.Format("Something failed more than {0} times. That is the last exception catched.", triesAmount, ex);
}

【讨论】:

    【解决方案2】:

    我编写了以下代码来做基本相同的事情。它还允许您指定要捕获的 Exception 的类型以及确定当前迭代是否应该抛出异常或继续重试的 Func

    public static void RetryBeforeThrow<T>(
        this Action action, 
        Func<T, int, bool> shouldThrow, 
        int waitTime) where T : Exception
    {
        if (action == null)
            throw new ArgumentNullException("action");
        if (shouldThrow == null)
            throw new ArgumentNullException("shouldThrow");
        if (waitTime <= 0)
            throw new ArgumentException("Should be greater than zero.", "waitTime");
        int tries = 0;
        do
        {
            try
            {
                action();
                return;
            }
            catch (T ex)
            {
                if (shouldThrow(ex, ++tries))
                    throw;
                Thread.Sleep(waitTime);
            }
        }
        while (true);
    }
    

    那你就可以这样称呼了

    Action a = () => 
        { 
            //do stuff 
        };
    a.RetryBeforeThrow<Exception>((e, i) => i >= 5, 1000);
    

    并且你可以指定任何异常类型,你可以在Func中检查异常,以确定它是要抛出还是继续重试的异常。这使您能够在 Action 中抛出您自己的异常,从而阻止重试的发生。

    【讨论】:

    • 我不会使用扩展方法来做到这一点,但是公开异常的委托方法是一个很好的解决方案。我会采纳的。谢谢
    【解决方案3】:

    我看不出代码有什么问题,我只是质疑你的假设。

    我看到的几个问题:

    • 客户端需要了解被调用动作的失败模式,才能选择正确的参数。
    • 操作可能会间歇性失败,这是容量杀手。这样的代码无法很好地扩展。
    • 客户端可以等待不确定的时间来完成操作。
    • 除最后一个以外的所有异常都将被吞并,这可能会隐藏重要的诊断信息。

    根据您的需要,您的代码可能就足够了,但要以更稳健的方式封装不可靠的资源,请查看circuit breaker 模式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      • 1970-01-01
      • 2016-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多