.net 如何为带参数的方法实现泛型重试模式

c3frrgcw  于 2023-03-24  发布在  .NET
关注(0)|答案(4)|浏览(121)

到目前为止,我实现了这个:

public class Retrier
{
    /// <summary>
    /// Execute a method with no parameters multiple times with an interval
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="function"></param>
    /// <param name="tryTimes"></param>
    /// <param name="interval"></param>
    /// <returns></returns>
    public static TResult Execute<TResult>(Func<TResult> function, int tryTimes, int interval)
    {
        for (int i = 0; i < tryTimes - 1; i++)
        {
            try
            {
                return function();
            }
            catch (Exception)
            {
                Thread.Sleep(interval);
            }
        }

        return function();
    }

    /// <summary>
    /// Execute a method with 1 parameter multiple times with an interval
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="function"></param>
    /// <param name="arg1"></param>
    /// <param name="tryTimes"></param>
    /// <param name="interval"></param>
    /// <returns></returns>
    public static TResult Execute<T, TResult>(Func<T, TResult> function, T arg1, int tryTimes, int interval)
    {
        for (int i = 0; i < tryTimes - 1; i++)
        {
            try
            {
                return function(arg1);
            }
            catch (Exception)
            {
                Thread.Sleep(interval);
            }
        }

        return function(arg1);
    }

    /// <summary>
    /// Execute a method with 2 parameters multiple times with an interval
    /// </summary>
    /// <typeparam name="T1"></typeparam>
    /// <typeparam name="T2"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="function"></param>
    /// <param name="arg1"></param>
    /// <param name="arg2"></param>
    /// <param name="tryTimes"></param>
    /// <param name="interval"></param>
    /// <returns></returns>
    public static TResult Execute<T1, T2, TResult>(Func<T1, T2, TResult> function, T1 arg1, T2 arg2, int tryTimes, int interval)
    {
        for (int i = 0; i < tryTimes - 1; i++)
        {
            try
            {
                return function(arg1, arg2);
            }
            catch (Exception)
            {
                Thread.Sleep(interval);
            }
        }

        return function(arg1, arg2);
    }
}

问题是它变得很难维护。当我有一个包含更多变量的方法时,我需要实现另一个Execute方法。如果我需要改变任何东西,我必须在所有方法中改变它。
所以我想知道是否有一种干净的方法使它成为一个方法,可以处理任何数量的参数?

jv4diomz

jv4diomz1#

正如您所说,您当前的设计不支持n和n+1参数而无需更改代码。
这里有两个常见的做法,可以来拯救。

预先定义变量

如果您查看ActionFunc委托,则可以看到从没有参数到16个参数的多个变体。

Execute的情况下,你可以遵循同样的概念。这里的美妙之处在于,你可以通过T4生成这个代码。

预期匿名函数

而不是有少数重载,您可以涵盖所有情况下与此

public static TResult Execute<TResult>(Func<TResult> function, int tryTimes, int interval)

以及所有异步情况:

public static Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> function, int tryTimes, int interval)

这里的技巧是如何传递参数
一个二个一个一个
所以,我们用下面的四个方法,你可以涵盖所有的方法和函数,不管它们是同步的还是异步的

void Execute(Action method, int tryTimes, int interval)
TResult Execute<TResult>(Func<TResult> function, int tryTimes, int interval)
Task ExecuteAsync<TResult>(Func<Task> asyncMethod, int tryTimes, int interval)
Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> asyncFunction, int tryTimes, int interval)
uidvcgyl

uidvcgyl2#

您已经可以使用部分应用程序处理任意参数。

Retrier.Execute(()=>OtherFunc(1,"banana",DateTime.Now),tryTimes:3,interval:5000);

这是函数式编程 * 和 * LINQ中的一种常用技术-Where操作需要一个Func<T,bool>,它通常涉及T类型之外的变量和成员,例如:

var cutoff=DateTime.Today.AddDays(-5);

var newUsers=users.Where(user=>user.Created>cutoff);

这在每次调用委托时都会分配一个新的委托,这就是为什么一些库会为多个参数重载,纯粹是出于性能方面的考虑。

jm81lzqq

jm81lzqq3#

我做了一个名为Mulligan的nuget包来完成这个任务;如果你不想使用我的包,你至少可以看到一个重试的例子。我创建这个包是因为我想使用TestStack.White库之外的retry code,而不是每次都复制和粘贴它。Mulligan的核心功能可以在Retry.cs中找到。
你也可以看看Polly是如何实现它们的重试逻辑的,如果你真的需要自己重新实现它,而且它不是.NET代码,但是这个library也是一个彻底的实现。每当我有时间的时候,我计划尽可能多地查看这些库和移植到mulligan,所以你可能会发现这是一个有用的灵感来源。
希望这些并列的例子能为您提供一些很好的例子,以及不同成熟度级别的实现之间的进展。

acruukt9

acruukt94#

要想真正的通用,你的函数必须有各种各样的原型来处理不同数量的参数,out,ref,async等等。多么痛苦!幸运的是,它根本不需要处理参数。与其 Package 函数,不如 Package 该函数的 * 调用 *,包括参数的赋值。

public static void Execute(Action action, int tryTimes, int interval)
{
    for (int i = 0; i < tryTimes - 1; i++)
    {
        try
        {
            action();
        }
        catch (Exception)
        {
            Thread.Sleep(interval);
        }
    }

    action();
}

然后你只需要像这样调用它:

Execute( () => {
    Foo();  //Don't pass any arguments or read any result
}, 1, 2000);

或者

Execute( () => {
    var x = Foo(2);  //One numeric input and read the return value
}, 3, 1000);

或者

Execute( () => {
    Foo("Hello", out var x);  //Use one string argument and read the out parameter
}, 5, 2000);

如果你也想处理async,可以再添加一个prototype,这样调用者就可以访问任务了(所以你可以使用Task.Delay而不是Sleep):

public static async Task Execute(Func<Task> action, int tryTimes, int interval)
{
    for (int i = 0; i < tryTimes - 1; i++)
    {
        try
        {
            return await action();
        }
        catch (Exception)
        {
            await Task.Delay(interval);
        }
    }

    return await action();
}

然后像这样使用:

await Execute( async () => {
    var x = await Foo();
}, 5, 2000);

相关问题