Самый простой способ написать логику повторных попыток?

Вы также можете использовать HTML5 replaceState, если хотите изменить URL-адрес, но не хотите добавлять запись в историю браузера:

if (window.history.replaceState) {
   //prevents browser from storing history with each change:
   window.history.replaceState(statedata, title, url);
}

Это «сломало» функциональность кнопки «Назад». Это может потребоваться в некоторых случаях, например, в галерее изображений (где вы хотите, чтобы обратная кнопка возвращалась обратно на индексную страницу галереи, вместо того чтобы перемещаться по каждому просматриваемому вами изображению), предоставляя каждому изображению свой собственный уникальный URL.

417
задан abatishchev 20 May 2016 в 18:06
поделиться

10 ответов

Общие операторы catch, которые просто повторяют один и тот же вызов, могут быть опасными, если используются в качестве общего механизма обработки исключений. Сказав это, вот оболочка повтора на основе лямбда, которую вы можете использовать с любым методом. Я решил учитывать количество повторных попыток и тайм-аут повторной попытки в качестве параметров для большей гибкости:

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

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

        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    Thread.Sleep(retryInterval);
                }
                return action();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }
}

Теперь вы можете использовать этот служебный метод для выполнения логики повторных попыток:

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

или:

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

или:

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

Или вы даже можете выполнить перегрузку async .

538
ответ дан 22 November 2019 в 23:14
поделиться

Я бы добавил следующий код к принятому ответу

public static class Retry<TException> where TException : Exception //ability to pass the exception type
    {
        //same code as the accepted answer ....

        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++)
            {
                try
                {
                    return action();
                }
                catch (TException ex) //Usage of the exception type
                {
                    exceptions.Add(ex);
                    Thread.Sleep(retryInterval);
                }
            }

            throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
        }
    }

По сути, приведенный выше код делает класс Retry универсальным, чтобы вы могли передать тип исключения, которое вы хотите перехватить, для повторной попытки.

Теперь используйте его почти таким же образом, но указав тип исключения

Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
.
0
ответ дан Juan M. Elosegui 20 May 2016 в 18:06
поделиться
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);

Более современная версия с поддержкой async / await :

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);
42
ответ дан Drew Noakes 20 May 2016 в 18:06
поделиться

Этот метод позволяет повторения на определенных типах исключительной ситуации (сразу бросает других).

public static void DoRetry(
    List<Type> retryOnExceptionTypes,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
{
    for (var i = 0; i < retryCount; ++i)
    {
        try
        {
            actionToTry();
            break;
        }
        catch (Exception ex)
        {
            // Retries exceeded
            // Throws on last iteration of loop
            if (i == retryCount - 1) throw;

            // Is type retryable?
            var exceptionType = ex.GetType();
            if (!retryOnExceptionTypes.Contains(exceptionType))
            {
                throw;
            }

            // Wait before retry
            Thread.Sleep(msWaitBeforeEachRety);
        }
    }
}
public static void DoRetry(
    Type retryOnExceptionType,
    Action actionToTry,
    int retryCount = 5,
    int msWaitBeforeEachRety = 300)
        => DoRetry(new List<Type> {retryOnExceptionType}, actionToTry, retryCount, msWaitBeforeEachRety);

использование В качестве примера:

DoRetry(typeof(IOException), () => {
    using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(entryBytes, 0, entryBytes.Length);
    }
});
1
ответ дан 22 November 2019 в 23:14
поделиться

Я бы реализовал это:

public static bool Retry(int maxRetries, Func<bool, bool> method)
{
    while (maxRetries > 0)
    {
        if (method(maxRetries == 1))
        {
            return true;
        }
        maxRetries--;
    }
    return false;        
}

Я бы не использовал исключения так, как они используются в других примерах. Мне кажется, что если мы ожидаем возможности неудачи метода, его отказ не является исключением. Поэтому вызываемый мной метод должен возвращать true в случае успеха и false в случае неудачи.

Почему это Func , а не просто Func ? Так что, если я хочу, чтобы метод мог генерировать исключение при ошибке, у меня есть способ сообщить ему, что это последняя попытка.

Так что я мог бы использовать его с таким кодом, как:

Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       if (!succeeded && lastIteration)
       {
          throw new InvalidOperationException(...)
       }
       return succeeded;
   });

или

if (!Retry(5, delegate(bool lastIteration)
   {
       // do stuff
       return succeeded;
   }))
{
   Console.WriteLine("Well, that didn't work.");
}

Если передача параметра, который метод не использует, оказывается неудобной, тривиально реализовать перегрузку Retry , которая просто принимает Func

4
ответ дан 22 November 2019 в 23:14
поделиться
public delegate void ThingToTryDeletage();

public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime)
{
   while(true)
   {
      try
      {
        ThingToTryDelegate();
      } catch {

            if( --N == 0) throw;
          else Thread.Sleep(time);          
      }
}
-1
ответ дан 22 November 2019 в 23:14
поделиться

Вы также можете рассмотреть возможность добавления типа исключения, для которого вы хотите повторить попытку. Например, это исключение тайм-аута, которое вы хотите повторить? Исключение базы данных?

RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);

public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
    if (action == null)
        throw new ArgumentNullException("action");
    if (retryOnExceptionType == null)
        throw new ArgumentNullException("retryOnExceptionType");
    while (true)
    {
        try
        {
            action();
            return;
        }
        catch(Exception e)
        {
            if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
                throw;

            if (retryTimeout > 0)
                System.Threading.Thread.Sleep(retryTimeout);
        }
    }
}

Вы также можете заметить, что во всех других примерах есть аналогичная проблема с тестированием для повторных попыток == 0 и либо повторных попыток бесконечности, либо неспособности вызвать исключения при отрицательном значении. Также Sleep (-1000) завершится ошибкой в ​​блоках catch выше. Зависит от того, насколько «глупыми» будут люди, но защитное программирование никогда не повредит.

14
ответ дан 22 November 2019 в 23:14
поделиться

Или как насчет того, чтобы сделать это немного аккуратнее ....

int retries = 3;
while (retries > 0)
{
  if (DoSomething())
  {
    retries = 0;
  }
  else
  {
    retries--;
  }
}

Я считаю, что выброса исключений обычно следует избегать как механизма, если только вы не передаете их между границами (например, здание библиотека, которую могут использовать другие люди). Почему бы просто не вернуть команду DoSomething () true , если она была успешной, и false в противном случае?

EDIT: И это можно инкапсулировать внутри функции, как предлагали другие. Проблема только в том, что вы сами не пишете функцию DoSomething ()

0
ответ дан 22 November 2019 в 23:14
поделиться

Возможно, это плохая идея. Во-первых, это символизирует принцип «безумие - это делать одно и то же дважды и каждый раз ожидать разных результатов». Во-вторых, этот шаблон кодирования плохо сочетается с самим собой. Например:

Предположим, уровень сетевого оборудования повторно отправляет пакет три раза при сбое, ожидая, скажем, секунду между сбоями.

Теперь предположим, что уровень программного обеспечения повторно отправляет уведомление о сбое три раза при сбое пакета.

Теперь предположим, что уровень уведомлений повторно активирует уведомление три раза при сбое доставки уведомления.

Теперь предположим, что уровень отчетов об ошибках повторно активирует уровень уведомлений три раза при сбое уведомления.

А теперь предположим, что веб-сервер повторно активирует сообщение об ошибке три раза при сбое ошибки.

А теперь предположим, что веб-клиент повторно отправляет запрос три раза после получения ошибки от сервера.

Теперь предположим, что линия на сетевом коммутаторе, которая должна направлять уведомление администратору, отключена. Когда пользователь веб-клиента наконец получает сообщение об ошибке? Я делаю это примерно через двенадцать минут.

Чтобы вы не подумали, что это просто глупый пример: мы видели эту ошибку в пользовательском коде, хотя и намного хуже, чем я описал здесь. В конкретном коде клиента промежуток между возникновением состояния ошибки и окончательным сообщением об этом пользователю составлял несколько недель , потому что так много уровней автоматически повторяли попытки с ожиданиями. Только представьте, что произошло бы, если бы было десять повторных попыток вместо трех .

Обычно правильное действие при возникновении состояния ошибки - немедленно сообщить об этом и позволить пользователю решить, что делать. Если пользователь хочет создать политику автоматических повторных попыток, позвольте ему создать эту политику на соответствующем уровне в программной абстракции.

59
ответ дан 22 November 2019 в 23:14
поделиться

Разрешение на функции и сообщения о повторных попытках

public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
 Guard.IsNotNull(method, "method");            
 T retval = default(T);
 do
 {
   try
   {
     retval = method();
     return retval;
   }
   catch
   {
     onFailureAction();
      if (numRetries <= 0) throw; // improved to avoid silent failure
      Thread.Sleep(retryTimeout);
   }
} while (numRetries-- > 0);
  return retval;
}
15
ответ дан 22 November 2019 в 23:14
поделиться