40

我从来没有对异常处理的工作方式完全满意,有很多异常和 try/catch 带来的表(堆栈展开等),但它似乎在这个过程中破坏了很多 OO 模型。

无论如何,这是问题所在:

假设您有一些类包装或包含网络文件 IO 操作(例如,在某处的某个特定 UNC 路径读取和写入某些文件)。由于各种原因,您不希望这些 IO 操作失败,因此,如果您检测到它们失败,则重试它们并继续重试它们,直到它们成功或达到超时。我已经有一个方便的 RetryTimer 类,我可以实例化并使用它在重试之间休眠当前线程,并确定超时时间何时结束,等等。

问题是你在这个类的几个方法中有一堆 IO 操作,你需要将它们中的每一个都包装在 try-catch / retry 逻辑中。

这是一个示例代码片段:

RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
    try
    {
        // do some file IO which may succeed or fail
        success = true;
    }
    catch (IOException e)
    {
        if (fileIORetryTimer.HasExceededRetryTimeout)
        {
            throw e;
        }
        fileIORetryTimer.SleepUntilNextRetry();
    }
}

那么,如何避免为整个班级的每个文件 IO 操作重复大部分代码呢?我的解决方案是在执行传递给它的委托块的类中使用匿名委托块和单个方法。这让我可以用其他方法做这样的事情:

this.RetryFileIO( delegate()
    {
        // some code block
    } );

我有点喜欢这个,但它还有很多不足之处。我想听听其他人如何解决这类问题。

4

4 回答 4

14

这看起来是一个了解面向方面编程的绝佳机会。这是一篇关于.NET 中 AOP的好文章。一般的想法是您将跨功能关注点(即重试 x 小时)提取到一个单独的类中,然后您将注释任何需要以这种方式修改其行为的方法。这是它的外观(在 Int32 上有一个很好的扩展方法)

[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}
于 2008-08-05T09:43:55.000 回答
4

只是想知道,你觉得你的方法有什么不足之处?您可以将匿名委托替换为..命名的?代表,类似

    public delegate void IoOperation(params string[] parameters);

    public void FileDeleteOperation(params string[] fileName)
    {
        File.Delete(fileName[0]);
    }

    public void FileCopyOperation(params string[] fileNames)
    {
        File.Copy(fileNames[0], fileNames[1]);
    }

    public void RetryFileIO(IoOperation operation, params string[] parameters)
    {
        RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
        bool success = false;
        while (!success)
        {
            try
            {
                operation(parameters);
                success = true;
            }
            catch (IOException e)
            {
                if (fileIORetryTimer.HasExceededRetryTimeout)
                {
                    throw;
                }
                fileIORetryTimer.SleepUntilNextRetry();
            }
        }
    }

    public void Foo()
    {
        this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete" );
        this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination" );
    }
于 2008-08-04T20:07:41.840 回答
2

您还可以使用更面向对象的方法:

  • 创建一个执行错误处理并调用抽象方法来执行具体工作的基类。(模板方法模式)
  • 为每个操作创建具体的类。

这具有命名您执行的每种操作类型的优点并为您提供命令模式 - 操作已表示为对象。

于 2008-08-07T11:30:38.853 回答
2

这是我最近所做的。它可能在其他地方做得更好,但它看起来非常干净且可重复使用。

我有一个看起来像这样的实用方法:

    public delegate void WorkMethod();

    static public void DoAndRetry(WorkMethod wm, int maxRetries)
    {
        int curRetries = 0;
        do
        {
            try
            {
                wm.Invoke();
                return;
            }
            catch (Exception e)
            {
                curRetries++;
                if (curRetries > maxRetries)
                {
                    throw new Exception("Maximum retries reached", e);
                }
            }
        } while (true);
    }

然后在我的应用程序中,我使用 c# 的 Lamda 表达式语法来保持整洁:

Utility.DoAndRetry( () => ie.GoTo(url), 5);

这会调用我的方法并重试多达 5 次。在第五次尝试时,在重试异常中重新抛出原始异常。

于 2010-09-13T02:25:02.620 回答