재시도 로직을 작성하는 가장 깨끗한 방법?


455

때로는 포기하기 전에 여러 번 작업을 다시 시도해야 할 때가 있습니다. 내 코드는 다음과 같습니다

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

다음과 같은 일반적인 재시도 기능으로 이것을 다시 작성하고 싶습니다.

TryThreeTimes(DoSomething);

C #에서 가능합니까? 이 TryThreeTimes()방법 의 코드는 무엇입니까 ?


1
간단한주기만으로는 충분하지 않습니까? 왜 여러 번 논리를 반복하고 실행하지 않는가?
Restuta

13
개인적으로, 나는 그러한 도우미 방법에 매우주의를 기울일 것입니다. 람다를 사용하여 구현할 수는 있지만 패턴 자체는 매우 냄새가 많이 나기 때문에 (자주 반복되는 것을 암시하는) 도우미를 도입하는 것은 그 자체로 매우 의심스럽고 전반적인 디자인이 잘못되었음을 강력하게 암시합니다.
Pavel Minaev

12
필자의 경우 DoSomething ()은 파일 삭제 또는 네트워크 포트 충돌과 같은 원격 시스템에서 작업을 수행합니다. 두 경우 모두 DoSomething이 성공할시기에 대한 주요 타이밍 문제가 있으며 원격으로 인해들을 수있는 이벤트가 없습니다. 네, 냄새가나요. 제안을 환영합니다.
noctonura

13
@PavelMinaev는 왜 재 시도를 사용하는 것이 전체적인 디자인이 나쁜지를 암시합니까? 통합 지점을 연결하는 많은 코드를 작성하는 경우 재 시도를 사용하는 것이 반드시 사용을 고려해야하는 패턴입니다.
bytedev

답변:


569

일반적인 예외 처리 메커니즘으로 사용하는 경우 단순히 동일한 호출을 재 시도하는 담요 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과부하 를 일으킬 수도 있습니다.


7
+1, 특히 경고 및 오류 검사의 경우. 그래도 예외 유형을 전달하여 일반 매개 변수 (T : Exception)로 잡으면 더 편할 것입니다.
TrueWill

1
"재시도"는 실제로 재 시도를 의미했습니다. 그러나 "시도"를 의미하도록 변경하는 것은 그리 어렵지 않습니다. 이름이 의미를 유지하는 한. 예를 들어, 음의 재시도 확인 또는 음의 시간 초과 등 코드를 개선 할 수있는 다른 기회가 있습니다. 나는 예제를 단순하게 유지하기 위해 대부분 이것을 생략했지만 ... 실제로는 아마도 구현을 향상시킬 것입니다.
LBushkin

40
우리는 대량의 Biztalk 앱에서 DB 액세스에 유사한 패턴을 사용하지만 두 가지 개선 사항이 있습니다. 재 시도해서는 안되는 예외에 대한 블랙리스트가 있으며, 가장 먼저 발생하는 예외를 저장하고 재 시도가 궁극적으로 실패 할 때 발생합니다. 두 번째 예외와 다음 예외가 종종 첫 번째 예외와 다른 이유입니다. 이 경우 마지막 예외 만 다시 던질 때 초기 문제를 숨 깁니다.
TToni

2
@Dexters 원래 예외를 내부 예외로하여 새 예외를 발생시킵니다. 원래 스택 추적은 내부 예외에서 속성으로 사용 가능합니다.
TToni

7
이를 처리하기 위해 Polly 와 같은 오픈 소스 라이브러리를 사용해 볼 수도 있습니다 . 재시도 사이에 대기하는 데 훨씬 더 많은 유연성이 있으며 프로젝트를 사용한 많은 다른 사람들에 의해 검증되었습니다. 예 : Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen

221

당신은 폴리 를 시도해야 . 개발자가 재시도, 재시도 재시도, 대기 및 재시도 또는 회로 차단기와 같은 일시적인 예외 처리 정책을 유창하게 표현할 수있는 저에 의해 작성된 .NET 라이브러리입니다.

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());

1
실제로 OnRetry 대리인은 무엇입니까? 예외가 발생했을 때 수행해야 할 작업이라고 가정합니다. 따라서 예외가 발생하면 OnRetry 델리게이트가 호출 한 후 델리게이트를 실행합니다. 그렇습니까?
user6395764

60

이것은 아마도 나쁜 생각입니다. 첫째, 그것은 "광기의 정의는 같은 일을 두 번하고 매번 다른 결과를 기대하고있다"는 최대의 상징이다. 둘째,이 코딩 패턴은 그 자체로 잘 구성되지 않습니다. 예를 들면 다음과 같습니다.

네트워크 하드웨어 계층이 실패 할 때 패킷을 세 번 다시 전송한다고 가정합시다 (예 : 실패 사이에 1 초).

이제 소프트웨어 계층이 패킷 실패시 3 회 실패에 대한 알림을 다시 보낸다고 가정합니다.

이제 알림 계층이 알림 배달 실패시 알림을 세 번 다시 활성화한다고 가정합니다.

이제 오류보고 계층이 알림 실패시 알림 계층을 세 번 다시 활성화한다고 가정합니다.

이제 웹 서버가 오류 실패시 오류보고를 세 번 다시 활성화한다고 가정합니다.

이제 웹 클라이언트가 서버에서 오류가 발생하면 요청을 세 번 다시 보낸다고 가정합니다.

이제 알림을 관리자에게 라우팅해야하는 네트워크 스위치의 회선이 분리되었다고 가정합니다. 웹 클라이언트 사용자는 언제 마지막으로 오류 메시지를 받습니까? 나는 약 12 ​​분 후에 그것을 만든다.

이것이 단지 어리석은 예라고 생각하지 않도록하십시오 : 우리는 여기에 설명 된 것보다 훨씬 나쁘지만 고객 코드 에서이 버그를 보았습니다. 특정 고객 코드에서 발생하는 오류 상태와 최종 사용자에게보고되는 간격은 몇 주가 지났으므로 많은 계층이 대기 상태에서 자동으로 다시 시도하기 때문입니다. 3 번이 아닌 10 번의 재 시도 가 있다면 어떻게 될지 상상해보십시오 .

일반적으로 오류 조건과 관련된 올바른 작업은 즉시보고하여 사용자가 수행 할 작업을 결정하도록하는 것입니다. 사용자가 자동 ​​재시도 정책을 작성하려면 소프트웨어 추상화에서 적절한 레벨로 해당 정책을 작성하십시오.



210
-1이 조언은 자동화 된 일괄 처리 시스템에서 발생하는 일시적인 네트워크 오류에는 쓸모가 없습니다.
nohat

15
이것이 "하지 마십시오"라고 말한 다음 "수행"인지 확실하지 않습니다. 이 질문을하는 대부분의 사람들은 아마도 소프트웨어 추상화에서 일하는 사람들 일 것입니다.
Jim L

44
웹 서비스와 같은 네트워크 리소스를 사용하는 배치 작업을 오래 실행하면 네트워크가 100 % 안정적 일 것으로 기대할 수 없습니다. 때때로 시간 초과, 소켓 연결 끊기, 사용 중 발생할 수있는 가짜 라우팅 결함 또는 서버 중단이 발생할 수 있습니다. 한 가지 옵션은 실패하는 것이지만 나중에 긴 작업을 다시 시작해야 할 수도 있습니다. 또 다른 옵션은 적절한 지연으로 몇 번 재 시도하여 일시적인 문제인지 확인한 다음 실패하는 것입니다. 나는 당신이 알아야 할 작곡에 동의합니다. 그러나 때로는 최선의 선택입니다.
Erik Funkenbusch

18
답을 시작할 때 사용한 인용문이 흥미 롭다고 생각합니다. "다른 결과를 기대하는 것"은 이전 경험이 정기적으로 동일한 결과를 제공하는 경우에만 광기입니다. 소프트웨어는 일관성을 보장하기 위해 구축되었지만 통제 할 수없는 외부의 신뢰할 수없는 힘과 상호 작용해야하는 상황이 있습니다.
Michael Richardson

49
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);

2
--retryCount <= 0재 시도를 0으로 설정하여 비활성화하려면 영원히 계속되므로 if to :를 변경하는 것이 좋습니다. 기술적으로이 용어 retryCount는 1로 설정하면 다시 시도하지 않기 때문에 실제로는 좋은 이름이 아닙니다. 그것은하는 tryCount뒤에 -를 붙이거나.
Stefanvds

2
@saille 동의합니다. 그러나 OP (및 다른 모든 답변)는을 사용하고 Thread.Sleep있습니다. 대안으로 타이머를 사용하거나 요즘 async재 시도 에 사용할 가능성이 더 높습니다 Task.Delay.
Drew Noakes

2
비동기 버전을 추가했습니다.
Drew Noakes

휴식 행동하면 returns true? Func<bool>
Kiquenet

32

과도 장애 관리 응용 프로그램 블록 을 포함하여 재시도 전략의 확장 모음을 제공합니다 :

  • 증분
  • 고정 간격
  • 지수 백 오프

또한 클라우드 기반 서비스에 대한 오류 감지 전략 모음이 포함되어 있습니다.

자세한 내용 은 개발자 안내서 의이 장 을 참조하십시오 .

NuGet을 통해 사용 가능합니다 ( ' topaz ' 검색 ).


1
흥미 롭군 Winforms 앱에서 Windows Azure 외부에서 사용할 수 있습니까?
Matthew Lock

6
물론. 핵심 재시도 메커니즘을 사용하고 고유 한 탐지 전략을 제공하십시오. 우리는 의도적으로 그것들을 분리했습니다. 핵심 nuget 패키지를 찾으십시오 : nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik

2
또한이 프로젝트는 현재 Apache 2.0 하에서 커뮤니티 기여를 받고 있습니다. aka.ms/entlibopen
Grigori Melnik

1
@ 알렉스. 그것의 조각은 플랫폼으로 만들고 있습니다.
Grigori Melnik

2
이것은 이제 더 이상 사용되지 않으며 마지막으로 사용했던 버그가 포함되어 있으며 github.com/MicrosoftArchive/… .
Ohad Schneider

15

기능 허용 및 메시지 재시도

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;
}

RetryMethod retval하는 방법 이 Truemax retries입니까?
Kiquenet

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 테스트와 비슷한 문제가 있으며 무한대 재시도 또는 음수 값이 주어지면 예외가 발생하지 않습니다. 또한 위의 catch 블록에서 Sleep (-1000)이 실패합니다. 사람들이 얼마나 '어리석은'것으로 예상 하느냐에 따라 방어 적 프로그래밍은 결코 아프지 않습니다.


9
+1, 그러나 RetryForException <T> (...)을 수행하지 않는 이유 : T : 예외, catch (T e)? 그냥 시도하고 완벽하게 작동합니다.
TrueWill

또는 이전에 일반 매개 변수가 트릭을 수행한다고 생각한 경우 Type으로 아무것도 수행 할 필요가 없으므로 여기에서 또는 여기에서.
csharptest.net

@TrueWill 분명히 캐치 (T 예)이 게시물에 따라 약간의 버그가 stackoverflow.com/questions/1577760/...
csharptest.net

3
업데이트 : 실제로 사용하고있는 더 나은 구현은 재 시도가 적절한 경우 true를 반환하는 Predicate <Exception> 대리자를 사용합니다. 이를 통해 기본 오류 코드 또는 예외의 다른 특성을 사용하여 재 시도가 가능한지 판별 할 수 있습니다. 예를 들어 HTTP 503 코드입니다.
csharptest.net

1
"또한 위의 catch 블록에서 Sleep (-1000)이 실패합니다"... TimeSpan을 사용하면이 문제가 발생하지 않습니다. 또한 TimeSpan은 훨씬 유연하고 자기 설명 적입니다. "int retryTimeout"의 서명에서 retryTimeout이 MS, 초, 분, 년인지 어떻게 알 수 있습니까? ;-)
bytedev

13

나는 재귀 및 확장 방법의 팬이므로 여기에 2 센트가 있습니다.

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}

7

이전 작업을 바탕으로 세 가지 방법으로 재시도 논리를 향상시키는 방법에 대해 생각했습니다.

  1. catch / retry 할 예외 유형 지정 모든 예외에 대한 재 시도는 명백한 잘못이므로 기본 향상입니다.
  2. try / catch에서 마지막 시도를 중첩하지 않고 약간 더 나은 성능을 달성합니다.
  3. 그것을 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 ) );

1
이것은 우수하다. 그러나 개인적으로 실제로 시간 제한이 아니기 때문에 'retryTimeout'이라고 부르지 않습니다. 아마도 'RetryDelay'?
Holf

7

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);
    }
}

2
궁금한 점이 있습니다. 이것은 동일한 대기 가능한 메소드를 반환하여 높은 재시도 횟수와 간격으로 엄청난 양의 스레드를 생성합니까?
HuntK24

6

폴리 사용

https://github.com/App-vNext/Polly-Samples

다음은 폴리와 함께 사용하는 재시도 제네릭입니다.

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);

5

최신 방식으로 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.


1
감사합니다, 파비안! 이것은 맨 위로 올라 가야합니다!
JamesHoux

1
@MarkLauter 짧은 대답은 예입니다. ;-)
Fabian Bigler

4

나는 이것을 구현할 것이다 :

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

다른 예제에서 사용 된 방식으로는 예외를 사용하지 않습니다. 방법이 성공하지 못할 가능성을 예상한다면 실패는 예외가 아닌 것 같습니다. 따라서 호출하는 메서드는 성공하면 true를, 실패하면 false를 반환해야합니다.

왜 A는 Func<bool, bool>a는 정당하고 있지 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>뿐만 아니라.


1
예외를 피하기 위해 +1. 비록 무효 재시도 (...)를하고 무언가를 던지더라도? 부울 리턴 및 / 또는 리턴 코드가 너무 자주 간과됩니다.
csharptest.net

1
"메소드가 성공하지 못할 가능성을 예상한다면 실패는 예외가 아니다"-어떤 경우에는 예외이지만 예외는 예외를 의미 할 필요는 없다. 오류 처리를위한 것입니다. 호출자가 부울 결과를 확인할 것이라는 보장은 없습니다. 이 있다 (아무것도 다른하지 않습니다 경우 응용 프로그램을 종료 런타임) 예외가 처리 될 것이라는 보장은.
TrueWill

참조를 찾을 수 없지만 .NET은 Exception을 "메서드가 수행 한 작업을 수행하지 않았다"고 정의합니다. 1 목적은 함수가 성공했는지 여부에 따라 호출자가 반환 값을 확인하도록 요구하는 Win32 패턴이 아닌 문제를 나타내는 데 예외를 사용하는 것입니다.
noctonura

그러나 예외가 단순히 "문제를 나타내는"것은 아닙니다. 또한 컴파일하는 데 시간과 메모리가 필요한 대량의 진단 정보도 포함됩니다. 그다지 중요하지 않은 상황이 분명히 있습니다. 그러나 그곳에는 많은 것이 있습니다. .NET은 제어 흐름에 예외를 사용하지 않으며 (파이썬이 StopIteration예외를 사용하는 것과 비교 ) 이유가 있습니다.
Robert Rossney

TryDo있어서 패턴 미끄러운 슬로프이다. 알기 전에 전체 호출 스택은 TryDo메소드 로 구성됩니다 . 그러한 혼란을 피하기 위해 예외가 발명되었습니다.
HappyNomad

2

지수 백 오프 는 단순히 x 번 시도하는 것보다 좋은 재시도 전략입니다. Polly와 같은 라이브러리를 사용하여 구현할 수 있습니다.


2

예외를 재 시도하거나 예외 유형을 명시 적으로 설정하는 옵션을 모두 원하는 경우 다음을 사용하십시오.

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);
    }
}

2

취소를 지원하는 방법이 필요했지만 중간 오류를 반환하는 지원을 추가했습니다.

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이와 같은 기능을 사용할 수 있습니다 . 10 초 지연으로 3 회 다시 시도하지만 취소하지 마십시오 .

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.
}

또는 취소하지 않는 한 5 초마다 영원히 다시 시도하십시오.

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.
}

당신이 짐작할 수 있듯이, 내 소스 코드 Retry에서 사용하려는 다른 델리게이트 유형을 지원하는 함수를 오버로드했습니다 .


2

이 방법은 특정 예외 유형에 대한 재 시도를 허용합니다 (다른 유형은 즉시 발생).

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

6 년 후 업데이트 : 이제 아래 접근 방식이 상당히 나쁘다고 생각합니다. 재시도 로직을 만들려면 Polly와 같은 라이브러리를 사용해야합니다.


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>


이것은 질문에 대한 답변을 제공하지 않습니다. 페이지 상단의 "질문과 대답"버튼을 사용하여 답변을 새로운 질문으로 게시 한 다음 질문에 대한 답변을 게시하여 커뮤니티와 배운 내용을 공유하십시오.
elixenide

C # 5.0에서는 codereview.stackexchange.com/q/55983/54000보다 훨씬 간단하지만 CansellactionToken을 삽입해야 할 수도 있습니다.
SerG

이 구현에 문제가 있습니다. 마지막 재시도 후 포기하기 직전의 Task.Delay이유는 없습니다.
HappyNomad

@HappyNomad 이것은 6 세의 답변이며 이제는 재시도 논리를 만드는 매우 나쁜 접근법이라고 생각합니다. 고려 사항에 따라 답변을 업데이트하겠습니다.
Cihan Uygun

0

아니면 좀 더 깔끔하게하는 것은 어떻습니까 ....

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

나는 다른 사람들이 사용할 수있는 라이브러리를 만드는 것과 같이 경계를 넘지 않는 한 예외를 던지는 것을 일반적으로 메커니즘으로 피해야한다고 생각합니다. 왜 바로이 DoSomething()명령의 반환 true은 성공하고있는 경우 false, 그렇지?

편집 : 그리고 이것은 다른 사람들이 제안한 것처럼 함수 안에 캡슐화 될 수 있습니다. 문제는 DoSomething()직접 함수를 작성하지 않는 경우입니다.


7
"예외를 던지는 것은 일반적으로 경계를 넘지 않는 한 메커니즘으로 피해야한다고 생각합니다."-나는 완전히 동의하지 않습니다. 발신자가 당신의 거짓 (또는 더 나쁜 null) 반환을 확인했는지 어떻게 알 수 있습니까? 왜 코드가 실패 했습니까? 거짓은 다른 말을하지 않습니다. 호출자가 실패를 스택으로 전달해야하는 경우 어떻게합니까? msdn.microsoft.com/en-us/library/ms229014.aspx를 읽으십시오. 이들은 라이브러리 용이지만 내부 코드와 마찬가지로 의미가 있습니다. 그리고 팀에서는 다른 사람들이 귀하의 코드를 호출 할 가능성이 있습니다.
TrueWill

0

재 시도하기 위해 메소드에 매개 변수를 전달하고 결과 값을 가져야했습니다. 그래서 나는 표현이 필요합니다. 나는 LBushkin에 영감을 얻은 작업을 수행하는이 클래스를 다음과 같이 사용할 수 있습니다.

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();
    }
}

추신. LBushkin의 솔루션은 다시 시도합니다 = D


0

허용 된 답변에 다음 코드를 추가합니다.

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));

TRY CATCH 루프의 코드가 예외없이 실행 되더라도 for 루프는 항상 retryCount에 따라 두 번 실행됩니다. try 루프에서 retryCount를 retry var와 동일하게 설정하여 for 루프가 그 이상 진행하지 않도록 제안합니다.
scre_www

@scre_www 당신이 착각했다고 생각합니다. 경우 action다음 throw하지 않습니다 Do수익을 따라서 break멀리에서 보내고 for루프.
HappyNomad

어쨌든이 구현에 문제가 있습니다. 마지막 재시도 후 포기하기 직전의 Thread.Sleep이유는 없습니다.
HappyNomad

0

나는이 대답이 매우 오래되었다는 것을 알고 있지만 카운터를 사용하여 모든 명령문을 사용하면서 문제가 발생했기 때문에 이것에 대해 의견을 말하고 싶었습니다.

수년에 걸쳐 나는 내가 생각하는 더 나은 접근법에 정착했다. 즉, 반응성 확장 "주제"등과 같은 일종의 이벤트 집계를 사용하는 것입니다. 시도가 실패하면 단순히 시도가 실패했다는 이벤트를 게시하고 수집기 기능이 이벤트를 다시 예약하도록합니다. 이를 통해 많은 재시도 루프로 호출 자체를 오염시키지 않고 재 시도를 훨씬 더 많이 제어 할 수 있습니다. 또한 여러 스레드 수면으로 단일 스레드를 묶지 않습니다.


0

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    
    }

0
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);
    }

당신은 무엇을 Request.BadRequest합니까?
Danh

0

재시도 도우미 : 리턴 가능 및 void 유형 재 시도를 모두 포함하는 일반 Java 구현입니다.

import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryHelper {
  private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
  private int retryWaitInMS;
  private int maxRetries;

  public RetryHelper() {
    this.retryWaitInMS = 300;
    this.maxRetries = 3;
  }

  public RetryHelper(int maxRetry) {
    this.maxRetries = maxRetry;
    this.retryWaitInMS = 300;
  }

  public RetryHelper(int retryWaitInSeconds, int maxRetry) {
    this.retryWaitInMS = retryWaitInSeconds;
    this.maxRetries = maxRetry;
  }

  public <T> T retryAndReturn(Supplier<T> supplier) {
    try {
      return supplier.get();
    } catch (Exception var3) {
      return this.retrySupplier(supplier);
    }
  }

  public void retry(Runnable runnable) {
    try {
      runnable.run();
    } catch (Exception var3) {
      this.retrySupplier(() -> {
        runnable.run();
        return null;
      });
    }

  }

  private <T> T retrySupplier(Supplier<T> supplier) {
    log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
    int retryCounter = 0;

    while(retryCounter < this.maxRetries) {
      try {
        return supplier.get();
      } catch (Exception var6) {
        ++retryCounter;
        log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
        if (retryCounter >= this.maxRetries) {
          log.error("Max retries exceeded.");
          throw var6;
        }

        try {
          Thread.sleep((long)this.retryWaitInMS);
        } catch (InterruptedException var5) {
          var5.printStackTrace();
        }
      }
    }

    return supplier.get();
  }

  public int getRetryWaitInMS() {
    return this.retryWaitInMS;
  }

  public int getMaxRetries() {
    return this.maxRetries;
  }
}

용법:

    try {
      returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
      //or no return type:
      new RetryHelper().retry(() -> mytask(args));
    } catch(Exception ex){
      log.error(e.getMessage());
      throw new CustomException();
    }

0

다음은 예외를 집계하고 취소를 지원 하는 async/ await버전입니다.

/// <seealso href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry"/>
protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
{
    var exceptions = new List<Exception>();

    for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
        try {
            return await action().ConfigureAwait( false );
        } catch ( Exception ex ) {
            exceptions.Add( ex );

            if ( retries < maxRetries )
                await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
            else
                throw new AggregateException( "Retry limit reached", exceptions );
        }

    exceptions.Add( new OperationCanceledException( cancelToken ) );
    throw new AggregateException( "Retry loop was canceled", exceptions );
}

-1
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;이것이 무한 루프가 종료되는 유일한 방법 이기 때문에이 방법은 실제로 "N 번 실패 할 때까지 시도"를 구현하고 원하는 " N성공할 때까지 여러 번 시도"하지 않습니다 . 당신은 필요 break;또는 return;호출 후 할 ThingToTryDelegate();그렇지 않으면 그것은 결코 실패하지 않는다 지속적 경우 호출 할 수 있습니다. 또한의 첫 번째 매개 변수에 TryNTimes이름 이 없으므로 컴파일되지 않습니다 . -1.
BACON

-1

여기에 게시 된 답변을 기반으로 작은 수업을 작성했습니다. 잘하면 그것은 누군가를 도울 것입니다 : 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

}

-1

나는 받아 들여진 대답의 비동기 버전을 구현했으며 어떤 의견도 훌륭하게 작동하는 것 같습니다.


        public static async Task DoAsync(
            Action action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            DoAsync<object>(() =>
            {
                action();
                return null;
            }, retryInterval, maxAttemptCount);
        }

        public static async Task<T> DoAsync<T>(
            Func<Task<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 await action();
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            throw new AggregateException(exceptions);
        }

그리고 간단히 다음과 같이 호출하십시오.

var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);

Thread.Sleep? 스레드를 차단하면 비동기 성의 이점이 사라집니다. 또한 Task DoAsync()버전이 type 인수를 허용해야한다고 확신합니다 Func<Task>.
Theodor Zoulias
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.