Еще один ответ, который легко отслеживать и довольно эффективен для операции O (n) без использования небезопасного кода или копирования частей исходных массивов.
Обязательно проверьте. Некоторые из предложений, найденных в этой теме, восприимчивы к ситуациям.
static void Main(string[] args)
{
// 1 1 1 1 1 1 1 1 1 1 2 2 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
byte[] buffer = new byte[] { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 5, 0, 5, 5, 1, 2 };
byte[] beginPattern = new byte[] { 1, 0, 2 };
byte[] middlePattern = new byte[] { 8, 9, 10 };
byte[] endPattern = new byte[] { 9, 10, 11 };
byte[] wholePattern = new byte[] { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
byte[] noMatchPattern = new byte[] { 7, 7, 7 };
int beginIndex = ByteArrayPatternIndex(buffer, beginPattern);
int middleIndex = ByteArrayPatternIndex(buffer, middlePattern);
int endIndex = ByteArrayPatternIndex(buffer, endPattern);
int wholeIndex = ByteArrayPatternIndex(buffer, wholePattern);
int noMatchIndex = ByteArrayPatternIndex(buffer, noMatchPattern);
}
/// <summary>
/// Returns the index of the first occurrence of a byte array within another byte array
/// </summary>
/// <param name="buffer">The byte array to be searched</param>
/// <param name="pattern">The byte array that contains the pattern to be found</param>
/// <returns>If buffer contains pattern then the index of the first occurrence of pattern within buffer; otherwise, -1</returns>
public static int ByteArrayPatternIndex(byte[] buffer, byte[] pattern)
{
if (buffer != null && pattern != null && pattern.Length <= buffer.Length)
{
int resumeIndex;
for (int i = 0; i <= buffer.Length - pattern.Length; i++)
{
if (buffer[i] == pattern[0]) // Current byte equals first byte of pattern
{
resumeIndex = 0;
for (int x = 1; x < pattern.Length; x++)
{
if (buffer[i + x] == pattern[x])
{
if (x == pattern.Length - 1) // Matched the entire pattern
return i;
else if (resumeIndex == 0 && buffer[i + x] == pattern[0]) // The current byte equals the first byte of the pattern so start here on the next outer loop iteration
resumeIndex = i + x;
}
else
{
if (resumeIndex > 0)
i = resumeIndex - 1; // The outer loop iterator will increment so subtract one
else if (x > 1)
i += (x - 1); // Advance the outer loop variable since we already checked these bytes
break;
}
}
}
}
}
return -1;
}
/// <summary>
/// Returns the indexes of each occurrence of a byte array within another byte array
/// </summary>
/// <param name="buffer">The byte array to be searched</param>
/// <param name="pattern">The byte array that contains the pattern to be found</param>
/// <returns>If buffer contains pattern then the indexes of the occurrences of pattern within buffer; otherwise, null</returns>
/// <remarks>A single byte in the buffer array can only be part of one match. For example, if searching for 1,2,1 in 1,2,1,2,1 only zero would be returned.</remarks>
public static int[] ByteArrayPatternIndex(byte[] buffer, byte[] pattern)
{
if (buffer != null && pattern != null && pattern.Length <= buffer.Length)
{
List<int> indexes = new List<int>();
int resumeIndex;
for (int i = 0; i <= buffer.Length - pattern.Length; i++)
{
if (buffer[i] == pattern[0]) // Current byte equals first byte of pattern
{
resumeIndex = 0;
for (int x = 1; x < pattern.Length; x++)
{
if (buffer[i + x] == pattern[x])
{
if (x == pattern.Length - 1) // Matched the entire pattern
indexes.Add(i);
else if (resumeIndex == 0 && buffer[i + x] == pattern[0]) // The current byte equals the first byte of the pattern so start here on the next outer loop iteration
resumeIndex = i + x;
}
else
{
if (resumeIndex > 0)
i = resumeIndex - 1; // The outer loop iterator will increment so subtract one
else if (x > 1)
i += (x - 1); // Advance the outer loop variable since we already checked these bytes
break;
}
}
}
}
if (indexes.Count > 0)
return indexes.ToArray();
}
return null;
}
Операторы выписки бланкета, которые просто повторяют один и тот же вызов, могут быть опасными, если они используются как общий механизм обработки исключений. Сказав это, вот обертка повторных попыток на основе лямбда, которую вы можете использовать любым способом. Я выбрал фактор количества повторений и тайм-аут повтора в качестве параметров для большей гибкости:
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
.
Я знаю, что этот ответ очень старый, но я просто хотел прокомментировать это, потому что я столкнулся с проблемами, используя их, делая, независимо от утверждения с помощью счетчиков.
На протяжении многих лет я думаю о лучшем подходе. То есть использовать какую-то агрегацию событий, например, реактивные расширения «Тема» или тому подобное. Когда попытка не выполняется, вы просто публикуете событие, в котором говорится, что попытка не выполнена, и функция агрегатора перераспределяет событие. Это позволяет вам намного больше контролировать повторение, не загрязняя сам вызов кучей циклов повтора, а что нет. Также вы не связываете нить с кучей нитей.
Разрешение функций и повторных сообщений
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;
}
Сделать это просто на C #, Java или других языках:
internal class ShouldRetryHandler {
private static int RETRIES_MAX_NUMBER = 3;
private static int numberTryes;
public static bool shouldRetry() {
var statusRetry = false;
if (numberTryes< RETRIES_MAX_NUMBER) {
numberTryes++;
statusRetry = true;
//log msg -> 'retry number' + numberTryes
}
else {
statusRetry = false;
//log msg -> 'reached retry number limit'
}
return statusRetry;
}
}
и использовать его в коде очень просто:
void simpleMethod(){
//some code
if(ShouldRetryHandler.shouldRetry()){
//do some repetitive work
}
//some code
}
или вы можете использовать его в рекурсивном методы:
void recursiveMethod(){
//some code
if(ShouldRetryHandler.shouldRetry()){
recursiveMethod();
}
//some code
}
int retries = 3;
while (true)
{
try
{
//Do Somthing
break;
}
catch (Exception ex)
{
if (--retries == 0)
return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
else
System.Threading.Thread.Sleep(100);
}
Для тех, кто хочет иметь возможность повторить попытку в любом исключении или явно установить тип исключения, используйте это:
public class RetryManager
{
public void Do(Action action,
TimeSpan interval,
int retries = 3)
{
Try<object, Exception>(() => {
action();
return null;
}, interval, retries);
}
public T Do<T>(Func<T> action,
TimeSpan interval,
int retries = 3)
{
return Try<T, Exception>(
action
, interval
, retries);
}
public T Do<E, T>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
return Try<T, E>(
action
, interval
, retries);
}
public void Do<E>(Action action,
TimeSpan interval,
int retries = 3) where E : Exception
{
Try<object, E>(() => {
action();
return null;
}, interval, retries);
}
private T Try<T, E>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
var exceptions = new List<E>();
for (int retry = 0; retry < retries; retry++)
{
try
{
if (retry > 0)
Thread.Sleep(interval);
return action();
}
catch (E ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
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);
--retryCount <= 0
, потому что это будет продолжаться вечно, если вы хотите отключить повторы, установив его на 0. Технически термин retryCount
не является действительно хорошим именем, потому что он не будет повторять попытку, если вы установите это к 1. либо переименовать его в tryCount
, либо поставить позади.
– Stefanvds
31 May 2016 в 06:47
Thread.Sleep
. Альтернативы - использовать таймеры или, скорее всего, в настоящее время использовать async
для повторного запуска с помощью Task.Delay
.
– Drew Noakes
4 October 2017 в 10:22
Возможно, это плохая идея. Во-первых, это символ символики «определение безумия делает одно и то же дважды и каждый раз ожидает разные результаты». Во-вторых, эта схема кодирования не скомбинирована с самим собой. Например:
Предположим, что ваш сетевой аппаратный уровень трижды повторяет пакет при сбое, ожидая, скажем, секунды между отказами.
Теперь предположим, что программный уровень повторно отправляет уведомление о сбое три раза при сбое пакетов.
Теперь предположим, что уровень уведомления повторно активирует уведомление три раза при сбое доставки уведомлений.
Теперь предположим, что слой сообщения об ошибках трижды активирует слой уведомления при сбое уведомления.
И теперь предположим, что веб-сервер повторно реагирует на сообщения об ошибках три раза при сбое ошибки.
Теперь предположим, что веб-клиент повторно отправляет запрос три раза при получении ошибки с сервера.
Теперь предположим, что линия сетевого коммутатора, которая должна перенаправить уведомление администратору, отключена. Когда пользователь веб-клиента наконец получит сообщение об ошибке? Я делаю это примерно через двенадцать минут.
Чтобы вы не подумали, что это всего лишь глупый пример: мы видели эту ошибку в коде клиента, хотя и намного хуже, чем я описал здесь. В конкретном коде клиента разрыв между состоянием ошибки и, наконец, сообщением пользователю, был несколько недель , потому что так много уровней автоматически повторялось с ожиданием. Представьте себе, что произойдет, если вместо три будут попытки повторить десять .
Обычно правильная вещь, связанная с условием ошибки, - report это немедленно и позволить пользователю решить, что делать. Если пользователь хочет создать политику автоматических попыток, пусть они создают эту политику на соответствующем уровне в абстракции программного обеспечения.
Использовать Polly
https://github.com/App-vNext/Polly-Samples
Вот повторитель, который я использую с Polly
public T Retry<T>(Func<T> action, int retryCount = 0)
{
PolicyResult<T> policyResult = Policy
.Handle<Exception>()
.Retry(retryCount)
.ExecuteAndCapture<T>(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
Используйте его так
var result = Retry(() => MyFunction()), 3);
Реализован ответ LBushkin последним способом:
public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
await task();
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
return await task();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
и использовать его:
await Retry.Do([TaskFunction], retryInterval, retryAttempts);
, тогда как функция [TaskFunction]
может быть либо Task<T>
, либо просто Task
.
Основываясь на предыдущей работе, я подумал о том, чтобы усилить логику повтора тремя способами:
Action
метода расширения static class ActionExtensions
{
public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
{
if (action == null)
throw new ArgumentNullException("action");
while( retries-- > 0 )
{
try
{
action( );
return;
}
catch (T)
{
Thread.Sleep( retryDelay );
}
}
action( );
}
}
Затем метод можно вызвать таким образом (конечно, можно использовать и анонимные методы):
new Action( AMethodThatMightThrowIntermittentException )
.InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
Моя async
реализация метода повтора:
public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
await Task.Delay(retryInterval).ConfigureAwait(false);
}
throw new AggregateException(exceptions);
}
Ключевые моменты: вместо этого я использовал .ConfigureAwait(false);
и Func<dynamic>
Func<T>
Держите его простым с C # 6.0
public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
try
{
return action();
}
catch when (retryCount != 0)
{
await Task.Delay(retryInterval);
return await Retry(action, retryInterval, --retryCount);
}
}
Я бы добавил следующий код к принятому ответу
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));
Вы должны попробовать Полли . Это библиотека .NET, написанная мной, которая позволяет разработчикам свободно передавать временные политики обработки исключений, такие как Retry, Retry Forever, Wait and Retry или Circuit Breaker.
Policy
.Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
.Execute(() => DoSomething());
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);
}
}
throw;
является единственным способом завершения бесконечного цикла, этот метод фактически реализует «try», пока не завершится N раз ». а не желаемое «до времени N
до достижения успеха». Вам понадобится break;
или return;
после вызова ThingToTryDelegate();
, иначе он будет вызываться непрерывно, если он никогда не сработает. Кроме того, это не будет компилироваться, потому что первый параметр TryNTimes
не имеет имени. -1.
– BACON
7 September 2017 в 07:16
Я поклонник методов рекурсии и расширения, так что вот мои два цента:
public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
try
{
@this();
}
catch
{
if (numberOfRetries == 0)
throw;
InvokeWithRetries(@this, --numberOfRetries);
}
}
Блок приложений Transient Fault Handling предоставляет расширяемый набор стратегий повторения, включая:
Оно также включает коллекцию стратегий обнаружения ошибок для облачных сервисов.
Подробнее см. в этой главе Руководства разработчика.
Доступно через NuGet (поиск « топаз »).
Или как насчет того, чтобы делать это немного аккуратно ....
int retries = 3;
while (retries > 0)
{
if (DoSomething())
{
retries = 0;
}
else
{
retries--;
}
}
Я полагаю, что исключение исключений в металле можно исключить как механизм, если вы не проходите мимо них между границами (например, люди могут использовать). Почему бы просто не вернуть команду DoSomething()
true
, если она была успешной, и false
в противном случае?
EDIT: И это может быть инкапсулировано внутри функции, как и другие. Проблема только в том, что вы сами не записываете функцию DoSomething()
Я написал небольшой класс на основе ответов, размещенных здесь. Надеюсь, это поможет кому-то: https://github.com/natenho/resiliency
using System;
using System.Threading;
/// <summary>
/// Classe utilitária para suporte a resiliência
/// </summary>
public sealed class Resiliency
{
/// <summary>
/// Define o valor padrão de número de tentativas
/// </summary>
public static int DefaultRetryCount { get; set; }
/// <summary>
/// Define o valor padrão (em segundos) de tempo de espera entre tentativas
/// </summary>
public static int DefaultRetryTimeout { get; set; }
/// <summary>
/// Inicia a parte estática da resiliência, com os valores padrões
/// </summary>
static Resiliency()
{
DefaultRetryCount = 3;
DefaultRetryTimeout = 0;
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
public static void Try(Action action)
{
Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
{
Try<Exception>(action, retryCount, retryTimeout, null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
{
Try<Exception>(action, retryCount, retryTimeout, tryHandler);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
{
Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try<TException>(Action action) where TException : Exception
{
Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount"></param>
public static void Try<TException>(Action action, int retryCount) where TException : Exception
{
Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount"></param>
/// <param name="retryTimeout"></param>
public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
{
Try<TException>(action, retryCount, retryTimeout, null);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/>
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
{
Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
}
/// <summary>
/// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
/// </summary>
/// <param name="action">Ação a ser realizada</param>
/// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
/// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
/// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
/// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
{
if (action == null)
throw new ArgumentNullException(nameof(action));
while (retryCount-- > 0)
{
try
{
action();
return;
}
catch (TException ex)
{
//Executa o manipulador de exception
if (tryHandler != null)
{
var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
tryHandler(callback);
//A propriedade que aborta pode ser alterada pelo cliente
if (callback.AbortRetry)
throw;
}
//Aguarda o tempo especificado antes de tentar novamente
Thread.Sleep(retryTimeout);
}
}
//Na última tentativa, qualquer exception será lançada de volta ao chamador
action();
}
}
/// <summary>
/// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
/// </summary>
public class ResiliencyTryHandler<TException> where TException : Exception
{
#region Properties
/// <summary>
/// Opção para abortar o ciclo de tentativas
/// </summary>
public bool AbortRetry { get; set; }
/// <summary>
/// <see cref="Exception"/> a ser tratada
/// </summary>
public TException Exception { get; private set; }
/// <summary>
/// Identifca o número da tentativa atual
/// </summary>
public int CurrentTry { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Instancia um manipulador de tentativa. É utilizado internamente
/// por <see cref="Resiliency"/> para permitir que o cliente altere o
/// comportamento do ciclo de tentativas
/// </summary>
public ResiliencyTryHandler(TException exception, int currentTry)
{
Exception = exception;
CurrentTry = currentTry;
}
#endregion
}
Мне нужно было передать некоторый параметр моему методу для повторения и получить значение результата; так что мне нужно выражение .. Я создаю этот класс, который выполняет эту работу (она вдохновлена на LBushkin's), вы можете использовать ее следующим образом:
static void Main(string[] args)
{
// one shot
var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
// delayed execute
var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
var res2 = retry.Execute();
}
static void fix()
{
Console.WriteLine("oh, no! Fix and retry!!!");
}
static string retryThis(string tryThis)
{
Console.WriteLine("Let's try!!!");
throw new Exception(tryThis);
}
public class Retry<TResult>
{
Expression<Func<TResult>> _Method;
int _NumRetries;
TimeSpan _RetryTimeout;
Action _OnFailureAction;
public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
_Method = method;
_NumRetries = numRetries;
_OnFailureAction = onFailureAction;
_RetryTimeout = retryTimeout;
}
public TResult Execute()
{
TResult result = default(TResult);
while (_NumRetries > 0)
{
try
{
result = _Method.Compile()();
break;
}
catch
{
_OnFailureAction();
_NumRetries--;
if (_NumRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(_RetryTimeout);
}
}
return result;
}
public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
return retry.Execute();
}
}
ps. решение Л.Бушкина делает еще один повтор = D
Мне нужен был метод, который поддерживает отмену, в то время как я был на нем, я добавил поддержку для возврата промежуточных отказов.
public static class ThreadUtils
{
public static RetryResult Retry(
Action target,
CancellationToken cancellationToken,
int timeout = 5000,
int retries = 0)
{
CheckRetryParameters(timeout, retries)
var failures = new List<Exception>();
while(!cancellationToken.IsCancellationRequested)
{
try
{
target();
return new RetryResult(failures);
}
catch (Exception ex)
{
failures.Add(ex);
}
if (retries > 0)
{
retries--;
if (retries == 0)
{
throw new AggregateException(
"Retry limit reached, see InnerExceptions for details.",
failures);
}
}
if (cancellationToken.WaitHandle.WaitOne(timeout))
{
break;
}
}
failures.Add(new OperationCancelledException(
"The Retry Operation was cancelled."));
throw new AggregateException("Retry was cancelled.", failures);
}
private static void CheckRetryParameters(int timeout, int retries)
{
if (timeout < 1)
{
throw new ArgumentOutOfRangeException(...
}
if (retries < 0)
{
throw new ArgumentOutOfRangeException(...
}
}
public class RetryResult : IEnumerable<Exception>
{
private readonly IEnumerable<Exception> failureExceptions;
private readonly int failureCount;
protected internal RetryResult(
ICollection<Exception> failureExceptions)
{
this.failureExceptions = failureExceptions;
this.failureCount = failureExceptions.Count;
}
}
public int FailureCount
{
get { return this.failureCount; }
}
public IEnumerator<Exception> GetEnumerator()
{
return this.failureExceptions.GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Вы можете использовать функцию Retry
, как это, повторите попытку 3 раза с задержка на 10 секунд, но без отмены.
try
{
var result = ThreadUtils.Retry(
SomeAction,
CancellationToken.None,
10000,
3);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// oops, 3 retries wasn't enough.
}
Или повторите попытку каждые пять секунд, если не будет отменена.
try
{
var result = ThreadUtils.Retry(
SomeAction,
someTokenSource.Token);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// operation was cancelled before success.
}
Как вы можете догадаться, ve перегрузил функцию Retry
для поддержки различных типов дегатов, которые я хочу использовать.
Я бы выполнил это:
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<bool, bool>
, а не только Func<bool>
? Поэтому, если я хочу , чтобы метод мог генерировать исключение при ошибке, у меня есть способ сообщить ему, что это последняя попытка.
Поэтому я могу использовать его с кодом типа:
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<bool>
.
StopIteration
), и есть причина.
– Robert Rossney
14 October 2009 в 09:56
Экспоненциальная отсрочка - хорошая стратегия повторения, чем просто попытка x раз. Вы можете использовать библиотеку, такую как Polly, для ее реализации.
Вы также можете подумать о добавлении типа исключения, для которого вы хотите повторить попытку. Например, это исключение таймаута, которое вы хотите повторить? Исключение базы данных?
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 и либо повторять бесконечность, либо не могут генерировать исключения при предоставлении отрицательного значения. Кроме того, спящий режим (-1000) выйдет из строя выше. Зависит от того, как «глупый» вы ожидаете, что люди будут только защитным программированием, никогда не болит.
Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
– Todd Meinershagen 25 August 2015 в 02:28