2016-10-06 4 views
0

Мне нужно повторить определенный метод, пока он не вернет непустой указатель.Как повторить попытку до тех пор, пока не будет выполнено какое-либо условие.

Существует удивительный answer, который повторяет попытку, исходя из того, существует ли исключение; однако я хотел бы обобщить этот класс, чтобы иметь возможность обрабатывать любое заданное условие.

Текущее использование будет выполнять действие определенное количество раз, пока не существует никаких исключений:

Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1)); 

или:

Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1)); 

или:

int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4); 

Как я могу изменить этот класс таким образом, чтобы он возвращался на основе возвращаемого значения функции, в которую я проходил?

Например, если я хотел повторить, пока моя функция не возвращается 3:

Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1)).Until(3); 

Что означало бы не выполнить SomeFunctionThatCanFail(), 1 раз в секунду, пока SomeFunctionThatCanFail() = 3?

Как бы я обобщил использование Retry.Do до тех пор, пока не будет выполнено условие?

public static class Retry 
{ 
    public static void Do(
     Action action, 
     TimeSpan retryInterval, 
     int retryCount = 3) 
    { 
     Do<object>(() => 
     { 
      action(); 
      return null; 
     }, retryInterval, retryCount); 
    } 

    public static T Do<T>(
     Func<T> action, 
     TimeSpan retryInterval, 
     int retryCount = 3) 
    { 
     var exceptions = new List<Exception>(); 

     for (int retry = 0; retry < retryCount; retry++) //I would like to change this logic so that it will retry not based on whether there is an exception but based on the return value of Action 
     { 
      try 
      { 
       if (retry > 0) 
        Thread.Sleep(retryInterval); 
       return action(); 
      } 
      catch (Exception ex) 
      { 
       exceptions.Add(ex); 
      } 
     } 

     throw new AggregateException(exceptions); 
    } 
} 
+0

Вы имеете в виду возвращаемое значение Func ? Действие не имеет возвращаемого значения. – EJoshuaS

+2

да действительно спасибо –

ответ

3

Как о создании следующий интерфейс:

public interface IRetryCondition<TResult> 
{ 
    TResult Until(Func<TResult, bool> condition); 
} 

public class RetryCondition<TResult> : IRetryCondition<TResult> 
{ 
    private TResult _value; 
    private Func<IRetryCondition<TResult>> _retry; 

    public RetryCondition(TResult value, Func<IRetryCondition<TResult>> retry) 
    { 
     _value = value; 
     _retry = retry; 
    } 

    public TResult Until(Func<TResult, bool> condition) 
    { 
     return condition(_value) ? _value : _retry().Until(condition); 
    } 
} 

И тогда вы будете обновлять свой Retry статический класс:

public static class Retry 
{ 
    // This method stays the same 
    // Returning an IRetryCondition does not make sense in a "void" action 
    public static void Do(
     Action action, 
     TimeSpan retryInterval, 
     int retryCount = 3) 
    { 
     Do<object>(() => 
     { 
      action(); 
      return null; 
     }, retryInterval, retryCount); 
    } 

    // Return an IRetryCondition<T> instance 
    public static IRetryCondition<T> Do<T>(
     Func<T> action, 
     TimeSpan retryInterval, 
     int retryCount = 3) 
    { 
     var exceptions = new List<Exception>(); 

     for (int retry = 0; retry < retryCount; retry++) 
     { 
      try 
      { 
       if (retry > 0) 
        Thread.Sleep(retryInterval); 

       // We return a retry condition loaded with the return value of action() and telling it to execute this same method again if condition is not met. 
       return new RetryCondition<T>(action(),() => Do(action, retryInterval, retryCount)); 
      } 
      catch (Exception ex) 
      { 
       exceptions.Add(ex); 
      } 
     } 

     throw new AggregateException(exceptions); 
    } 
} 

Вы сможете достичь что-то вроде следующего:

int result = Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1)).Until(r => r == 3); 

Более функциональный подход

Я пытался придумать более «функциональной ориентированном» решение (схожем с LINQ):

Во-первых, мы имели бы два интерфейса для выполнения действия:

public interface IRetryResult 
{ 
    void Execute(); 
} 

public interface IRetryResult<out TResult> 
{ 
    TResult Execute(); 
} 

Тогда нам понадобятся два интерфейса для настройки операции повтора:

public interface IRetryConfiguration : IRetryResult 
{ 
    IRetryConfiguration Times(int times); 
    IRetryConfiguration Interval(TimeSpan interval); 
} 

public interface IRetryConfiguration<out TResult> : IRetryResult<TResult> 
{ 
    IRetryConfiguration<TResult> Times(int times); 
    IRetryConfiguration<TResult> Interval(TimeSpan interval); 
    IRetryConfiguration<TResult> Until(Function<TResult, bool> condition); 
} 

Наконец, нам понадобятся две реализации для обоих Inte rfaces:

public class ActionRetryConfiguration : IRetryConfiguration 
{ 
    private readonly Action _action; 
    private readonly int? _times; 
    private readonly TimeSpan? _interval; 

    public ActionRetryConfiguration(Action action, int? times, TimeSpan? interval) 
    { 
     _action = action; 
     _times = times; 
     _interval = interval; 
    } 

    public void Execute() 
    { 
     Execute(_action, _times, _interval); 
    } 

    private void Execute(Action action, int? times, TimeSpan? interval) 
    { 
     action(); 
     if (times.HasValue && times.Value <= 1) return; 
     if (times.HasValue && interval.HasValue) Thread.Sleep(interval.Value); 
     Execute(action, times - 1, interval); 
    } 

    public IRetryConfiguration Times(int times) 
    { 
     return new ActionRetryConfiguration(_action, times, _interval); 
    } 

    public IRetryConfiguration Interval(TimeSpan interval) 
    { 
     return new ActionRetryConfiguration(_action, _times, interval); 
    } 
} 


public class FunctionRetryConfiguration<TResult> : IRetryConfiguration<TResult> 
{ 
    private readonly Func<TResult> _function; 
    private readonly int? _times; 
    private readonly TimeSpan? _interval; 
    private readonly Func<TResult, bool> _condition; 

    public FunctionRetryConfiguration(Func<TResult> function, int? times, TimeSpan? interval, Func<TResult, bool> condition) 
    { 
     _function = function; 
     _times = times; 
     _interval = interval; 
     _condition = condition; 
    } 

    public TResult Execute() 
    { 
     return Execute(_function, _times, _interval, _condition); 
    } 

    private TResult Execute(Func<TResult> function, int? times, TimeSpan? interval, Func<TResult, bool> condition) 
    { 
     TResult result = function(); 
     if (condition != null && condition(result)) return result; 
     if (times.HasValue && times.Value <= 1) return result; 
     if ((times.HasValue || condition != null) && interval.HasValue) Thread.Sleep(interval.Value); 
     return Execute(function, times - 1, interval, condition); 
    } 

    public IRetryConfiguration<TResult> Times(int times) 
    { 
     return new FunctionRetryConfiguration<TResult>(_function, times, _interval, _condition); 
    } 

    public IRetryConfiguration<TResult> Interval(TimeSpan interval) 
    { 
     return new FunctionRetryConfiguration<TResult>(_function, _times, interval, _condition); 
    } 

    public IRetryConfiguration<TResult> Until(Func<TResult, bool> condition) 
    { 
     return new FunctionRetryConfiguration<TResult>(_function, _times, _interval, condition); 
    } 
} 

И, наконец, Retry статический класс, точка входа:

public static class Retry 
{ 
    public static IRetryConfiguration Do(Action action) 
    { 
     return new ActionRetryConfiguration(action, 1, null); 
    } 

    public static IRetryConfiguration<TResult> Do<TResult>(Func<TResult> action) 
    { 
     return new FunctionRetryConfiguration<TResult>(action, 1, null, null); 
    } 
} 

Я думаю, что этот подход менее глючный, и чище.

Кроме того, это позволит вам делать такие вещи, как эти: Reactive Framework

int result = Retry.Do(SomeIntMethod).Interval(TimeSpan.FromSeconds(1)).Until(n => n > 20).Execute(); 

Retry.Do(SomeVoidMethod).Times(4).Execute(); 
+1

Это элегантное решение. –

0

Ну, если я правильно все понял, что-то, как это должно решить проблему:

public static T Do<T>(Func<T> action, TimeSpan retryInterval, Predicate<T> predicate) 
{ 
    var exceptions = new List<Exception>(); 
    try 
    { 
     bool succeeded; 
     T result; 
     do 
     { 
      result = action(); 
      succeeded = predicate(result); 
     } while (!succeeded); 

     return result; 
    } 
    catch (Exception ex) 
    { 
     exceptions.Add(ex); 
    } 
    throw new AggregateException(exceptions); 
} 

Добавить этот метод в класс повторных попыток.

Я попробовал его с образцом ConsoleApplication, с этим кодом:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var _random = new Random(); 

     Func<int> func =() => 
     { 
      var result = _random.Next(10); 
      Console.WriteLine(result); 
      return result; 
     }; 

     Retry.Do(func, TimeSpan.FromSeconds(1), i => i == 5); 

     Console.ReadLine(); 
    } 
} 

И действительно, он останавливается, когда он Randoms 5.

+1

Почему вы синхронно выполняете работу, а затем возвращаете «Задачу»? – Servy

+0

Ну, вместо 'T' Resharper сделал это' Задача' ... это исправить, я плохой. –

-1

Похоже, вы overthinking это:

int returnValue = -1; 
while (returnValue != 3) 
{ 
    returnValue = DoStuff(); 
    // DoStuff should include a step to avoid maxing out cpu 
} 
return returnValue; 

Конечно, «3» может быть переменной, что вы передаете в функцию.

1

от Microsoft (NuGet «Rx-Main») имеет все операторы уже построены делать такого рода вещи из коробки.

Попробуйте это:

IObservable<int> query = 
    Observable 
     .Defer(() => 
      Observable.Start(() => GetSomeValue())) 
     .Where(x => x == 1) 
     .Timeout(TimeSpan.FromSeconds(0.1)) 
     .Retry() 
     .Take(1); 

query 
    .Subscribe(x => 
    { 
     // Can only be a `1` if produced in less than 0.1 seconds 
     Console.WriteLine(x); 
    }); 
Смежные вопросы