Yeniden deneme mantığı yazmanın en temiz yolu?


455

Bazen vazgeçmeden önce birkaç kez tekrar denemem gerekiyor. Kodum şöyle:

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

Bu gibi genel bir yeniden deneme işlevinde yeniden yazmak istiyorum:

TryThreeTimes(DoSomething);

C # ile mümkün mü? TryThreeTimes()Yöntemin kodu ne olurdu ?


1
Basit bir döngü yeterli değil mi? Neden tekrar tekrar ve birkaç kez mantık yürütmüyorsunuz?
Restuta

13
Şahsen, böyle bir yardımcı yönteme son derece ihtiyatlı davranırım. Lambdalar kullanarak uygulamak kesinlikle mümkündür, ancak desenin kendisi son derece kötü kokulu, bu yüzden (bunun sık sık tekrarlandığı anlamına gelir) için bir yardımcı tanıtmak kendi başına son derece şüpheli ve kötü genel tasarıma güçlü bir şekilde işaret ediyor.
Pavel Minaev

12
Benim durumumda, DoSomething ()'lerim, dosyaları silmek veya bir ağ bağlantı noktasına vurmaya çalışmak gibi uzak makinelerde bir şeyler yapıyor. Her iki durumda da, DoSomething'in ne zaman başarılı olacağı konusunda önemli zamanlama sorunları var ve uzaklık nedeniyle dinleyebileceğim bir olay yok. Yani evet, koklamak. Öneriler hoş geldiniz.
noctonura

13
@PavelMinaev neden retries kullanmak kötü genel tasarım ipucu? Entegrasyon noktalarını birbirine bağlayan çok fazla kod yazarsanız, yeniden denemeleri kullanmak kesinlikle ciddi bir şekilde kullanmayı düşünmeniz gereken bir modeldir.
bytedev

Yanıtlar:


569

Genel istisna işleme mekanizması olarak kullanıldığında, aynı çağrıyı yeniden deneyen battaniye yakalama ifadeleri tehlikeli olabilir. Bunu söyledikten sonra, herhangi bir yöntemle kullanabileceğiniz lambda tabanlı bir yeniden deneme sarıcısı. Yeniden deneme sayısını ve yeniden deneme zaman aşımını biraz daha esneklik için parametreler olarak hesaplamayı seçtim:

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

Şimdi yeniden deneme mantığını gerçekleştirmek için bu yardımcı yöntemi kullanabilirsiniz:

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

veya:

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

veya:

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

Ya da asyncaşırı yükleme bile yapabilirsiniz .


7
+1, özellikle uyarı ve hata kontrolü için. Bu genel bir parametre (burada T: İstisna) olarak yakalamak için istisna türünde geçti, ben daha rahat olurdu.
22'de TrueWill

1
Niyetim "yeniden denemeler" in aslında yeniden denemeler demekti. Ancak bunu "deneme" anlamına gelecek şekilde değiştirmek çok zor değil. İsim anlamlı olduğu sürece. Örneğin, negatif yeniden denemeleri veya negatif zaman aşımlarını denetleme gibi kodu iyileştirmek için başka fırsatlar da vardır. Bunları çoğunlukla örneği basit tutmak için atladım ... ama yine de, pratikte bunlar muhtemelen uygulamada iyi geliştirmeler olacaktır.
LBushkin

40
DB erişimi için yüksek hacimli bir Biztalk Uygulamasında benzer bir desen kullanıyoruz, ancak iki iyileştirme var: Yeniden denenmemesi gereken istisnalar için kara listelerimiz var ve ortaya çıkan ilk istisnayı saklıyoruz ve yeniden deneme başarısız olduğunda atıyoruz. Bunun nedeni, ikinci ve sonraki istisnaların genellikle birincisinden farklı olmasıdır. Bu durumda, yalnızca son istisnayı yeniden düzenlerken ilk sorunu gizlersiniz.
TToni

2
@ Metinler İç istisna olarak orijinal istisna ile yeni bir istisna atarız. Orijinal yığın izlemesi, iç istisnalardan özellik olarak edinilebilir.
TToni

7
Bunu yapmak için Polly gibi bir açık kaynak kitaplığı kullanmayı da deneyebilirsiniz . Yeniden denemeler arasında beklemek için daha fazla esneklik var ve projeyi kullanan birçok kişi tarafından onaylandı. Örnek: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen

222

Polly'yi denemelisin . Geliştiricilerin Retry, Retry Forever, Wait and Retry veya Devre Kesici gibi geçici istisna işleme ilkelerini akıcı bir şekilde ifade etmelerini sağlayan bir .NET kütüphanesi.

Misal

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

1
Aslında OnRetry delegesi nedir? İstisna meydana geldiğinde gerçekleştirmemiz gereken şey budur. Bu nedenle istisna oluştuğunda OnRetry delegesi arayacak ve daha sonra delege yürütecaktır. Öyle mi?
user6395764

61

Bu muhtemelen kötü bir fikirdir. Birincisi, "deliliğin tanımı aynı şeyi iki kez yapmak ve her seferinde farklı sonuçlar beklemek" anlamına gelir. İkincisi, bu kodlama modeli kendiliğinden iyi oluşmaz. Örneğin:

Ağ donanım katmanınızın bir paketi üç kez hataya yolladığını varsayalım, örneğin hatalar arasında bir saniye bekleyin.

Şimdi yazılım katmanının paket arızasında üç kez bir arıza hakkında bir bildirim gönderdiğini varsayalım.

Şimdi, bildirim katmanının, bildirim teslim hatası durumunda bildirimi üç kez yeniden etkinleştirdiğini varsayalım.

Şimdi hata raporlama katmanının bir bildirim hatası durumunda bildirim katmanını üç kez yeniden etkinleştirdiğini varsayalım.

Ve şimdi web sunucusunun hata hatasıyla ilgili üç kez hata raporlamasını yeniden etkinleştirdiğini varsayalım.

Ve şimdi web istemcisinin sunucudan bir hata aldıktan sonra isteği üç kez yeniden gönderdiğini varsayalım.

Şimdi ağ anahtarında, bildirimi yöneticiye yönlendirmesi gereken hattın fişinin çekildiğini varsayalım. Web istemcisi kullanıcısı ne zaman hata mesajını alır? On iki dakika sonra yapıyorum.

Bunun sadece aptalca bir örnek olduğunu düşünürsek: bu hatayı müşteri kodunda gördük, ancak burada tarif ettiğimden çok daha kötü. Belirli müşteri kodunda, meydana gelen hata koşulu ile son olarak kullanıcıya bildirilen boşluk arasındaki fark birkaç hafta oldu çünkü pek çok katman otomatik olarak bekleme süreleriyle tekrar deniyordu. Üç yerine on tekrar deneme olsaydı ne olacağını hayal et .

Genellikle bir hata koşulu ile yapılacak doğru şey derhal bildirmek ve kullanıcının ne yapacağına karar vermesidir. Kullanıcı otomatik yeniden deneme politikası oluşturmak istiyorsa, bu politikayı yazılım soyutlamasında uygun düzeyde oluşturmasına izin verin.


19
+1. Raymond burada gerçek bir hayat örneği paylaşıyor, blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi

210
-1 Bu öneri otomatik toplu işlem sistemlerinin karşılaştığı geçici ağ hataları için işe yaramaz.
Nohat

15
Bunun "Yapma" ve ardından "yapma" dediklerinden emin değilim. Bu soruyu soran insanların çoğu muhtemelen yazılım soyutlamasında çalışan kişilerdir.
Jim L

44
Web hizmetleri gibi ağ kaynaklarını kullanan uzun süredir toplu işleriniz olduğunda, ağın% 100 güvenilir olmasını bekleyemezsiniz. Ara sıra zaman aşımları, soket bağlantı kesmeleri, hatta kullanıldığında meydana gelen sahte yönlendirme aksaklıkları veya sunucu kesintileri olacaktır. Bir seçenek başarısız olmaktır, ancak bu daha sonra uzun bir işi yeniden başlatmak anlamına gelebilir. Başka bir seçenek, geçici bir sorun olup olmadığını görmek için uygun bir gecikmeyle birkaç kez yeniden denemektir, sonra başarısız olun. Farkında olmanız gereken kompozisyon konusunda hemfikirim .. ama bazen en iyi seçim.
Erik Funkenbusch

18
Cevabınızın başında kullandığınız teklifin ilginç olduğunu düşünüyorum. "Farklı sonuçlar beklemek", ancak önceden deneyim düzenli olarak aynı sonuçları verirse deliliktir. Yazılım bir tutarlılık vaadi üzerine inşa edilmiş olsa da, kesinlikle kontrolümüz dışındaki güvenilmez güçlerle etkileşime girmemiz gereken durumlar vardır.
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);
        }
    }
}

O zaman şunu ararsınız:

TryThreeTimes(DoSomething);

...Veya alternatif olarak...

TryThreeTimes(() => DoSomethingElse(withLocalVariable));

Daha esnek bir seçenek:

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

Olarak kullanılacak:

DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);

Async / await desteğiyle daha modern bir sürüm:

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

Olarak kullanılacak:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);

2
tercihen if değerini şu şekilde değiştirin: --retryCount <= 0çünkü yeniden deneme sayısını 0 olarak ayarlayarak devre dışı bırakmak istiyorsanız sonsuza kadar devam edecektir. Teknik olarak terim retryCountgerçekten iyi bir isim değildir, çünkü 1 olarak ayarlarsanız yeniden denenmez. o kadar tryCountgeride - koyun ya.
Stefanvds

2
@saille katılıyorum. Ancak OP (ve diğer tüm cevaplar) kullanıyor Thread.Sleep. Alternatifleri zamanlayıcıları kullanmaktır ya da günümüzde asyncyeniden denemek için kullanmak daha olasıdır Task.Delay.
Drew Noakes

2
Zaman uyumsuz bir sürüm ekledim.
Drew Noakes

Sadece kırmak aksiyon eğer returns true? Func<bool>
Kiquenet

32

Geçici Arıza İdaresi uygulama bloğu dahil yeniden deneme stratejilerinin genişletilebilir koleksiyonunu sunar:

  • Artan
  • Sabit aralık
  • Üstel geri çekilme

Ayrıca bulut tabanlı hizmetler için bir hata algılama stratejileri koleksiyonu içerir.

Daha fazla bilgi için Geliştirici Kılavuzu'nun bu bölümüne bakın .

Aracılığıyla Mevcut Nuget ( 'için arama topaz ').


1
İlginç. Winforms uygulamasında bunu Windows Azure'un dışında kullanabilir misiniz?
Matthew Lock

6
Kesinlikle. Çekirdek yeniden deneme mekanizmasını kullanın ve kendi algılama stratejilerinizi sağlayın. Bunları kasten ayırdık. Çekirdek külçe paketini burada bulabilirsiniz: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik

2
Ayrıca, proje şimdi Apache 2.0 altında ve topluluk katkılarını kabul ediyor. aka.ms/entlibopen
Grigori Melnik

1
@Alex. Parçaları platforma dönüştürüyor.
Grigori Melnik

2
Bu artık kullanımdan kaldırıldı ve en son kullandığım kadarıyla bilmediğim kadarıyla bazı hatalar içeriyordu ve asla düzeltilmeyecekti: github.com/MicrosoftArchive/… .
Ohad Schneider

15

İşlevlere izin verme ve mesajları yeniden deneme

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 için retval Doğru, yamax retries?
Kiquenet

14

Yeniden denemek istediğiniz kural dışı durum türünü de ekleyebilirsiniz. Örneğin, bu yeniden denemek istediğiniz bir zaman aşımı istisnası mı? Bir veritabanı istisnası?

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

Diğer tüm örneklerin, yeniden deneme == 0 testi ile benzer bir sorunu olduğunu ve sonsuz yeniden deneme deneyini veya negatif bir değer verildiğinde istisnaları yükseltemediğini de unutmayın. Ayrıca yukarıdaki yakalama bloklarında Uyku (-1000) başarısız olur. İnsanların ne kadar 'aptal' olmasını beklediğinize bağlıdır, ancak savunma programlaması asla acıtmaz.


9
+1, ama neden RetryForException <T> (...) burada T: Exception, sonra yakalamak (T e)? Sadece denedim ve mükemmel çalışıyor.
TrueWill

Ya burada ya da Tip ile hiçbir şey yapmam gerekmediği için, sade eski bir parametrenin hile yapacağını düşündüm.
csharptest.net

@TrueWill görünüşe göre (T ex) bu yazıya göre bazı hatalar var stackoverflow.com/questions/1577760/…
csharptest.net

3
Güncelleme: Aslında kullandığım daha iyi bir uygulama, bir yeniden deneme uygunsa true döndüren bir Tahmin <İstisna> temsilcisini alır. Bu, yeniden denemenin geçerli olup olmadığını belirlemek için yerel hata kodlarını veya kural dışı durumun diğer özelliklerini kullanmanızı sağlar. Örneğin, HTTP 503 kodları.
csharptest.net

1
"Ayrıca yukarıdaki yakalama bloklarında Uyku (-1000) başarısız olur" ... bir TimeSpan kullanın ve bu sorunu alamazsınız. Ayrıca TimeSpan çok daha esnek ve kendini tanımlayıcıdır. "İnt retryTimeout" imzanızdan nasıl retryTimeout MS, saniye, dakika, yıl olup olmadığını nasıl bilebilirim? ;-)
bytedev

13

Ben özyineleme ve uzatma yöntemleri hayranıyım, işte iki sentim:

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

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

7

Önceki çalışmayı temel alarak, yeniden deneme mantığını üç şekilde geliştirmeyi düşündüm:

  1. Hangi istisna türünün yakalanacağını / yeniden deneneceğini belirtme. Herhangi bir istisna için yeniden denemek sadece yanlış olduğundan, bu birincil görevdir.
  2. Son denemeyi bir deneme / yakalamada yerleştirmemek, biraz daha iyi performans elde etmek
  3. Bir Actionuzatma yöntemi haline getirme

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

Yöntem daha sonra şu şekilde çağrılabilir (anonim yöntemler de elbette kullanılabilir):

new Action( AMethodThatMightThrowIntermittentException )
  .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );

1
Bu mükemmel. Ama kişisel olarak ben gerçekten 'Zaman aşımı' olmadığı için 'retryTimeout' demem. 'RetryDelay', belki?
Holf

7

C # 6.0 ile basit tutun

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
Biraz merak ediyorum, bu aynı beklenen yöntemi döndürdüğü için yüksek bir yeniden deneme sayısı ve aralığı olan çılgın bir iplik parçasını doğurur mu?
HuntK24

6

Polly kullanın

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

İşte Polly ile kullandığım bir yeniden deneme generic

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

Bu şekilde kullanın

var result = Retry(() => MyFunction()), 3);

5

LBushkin'in cevabını son moda uyguladı:

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

ve kullanmak için:

await Retry.Do([TaskFunction], retryInterval, retryAttempts);

oysa fonksiyon ya sadece [TaskFunction]ya Task<T>da sadece olabilir Task.


1
Teşekkürler Fabian! Bu, en üste çıkarılmalıdır!
JamesHoux

1
@MarkLauter kısa cevap evet. ;-)
Fabian Bigler

4

Bunu uygularım:

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

İstisnaları diğer örneklerde kullanıldığı şekilde kullanmam. Bana öyle geliyor ki, bir yöntemin başarılı olamayacağını tahmin edersek, başarısızlığı bir istisna değildir. Bu yüzden çağırdığım yöntem başarılı olursa true, başarısız olursa false döndürmelidir.

Neden Func<bool, bool>sadece bir değil de bir Func<bool>? Ben eğer Böylece istediğiniz bir yöntem başarısızlığı üzerinde bir istisna oluşturmaya muktedir, bu son deneme olduğunu bunu bildiren bir yolu var.

Yani kod gibi kullanabilirsiniz:

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

veya

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

Yöntem kullanımının zor olduğunu kanıtlamaktadır etmediğini bir parametre geçirerek, bu bir aşırı uygulamak için Önemsiz Retrysadece sürdüğünü Func<bool>de.


1
İstisnadan kaçınmak için +1. Yine de bir boşluk yeniden denemek (...) ve bir şey atmak? Boolean dönüşleri ve / veya dönüş kodları sıklıkla göz ardı edilir.
csharptest.net

1
"bir yöntemin başarılı olamayacağını tahmin edersek, başarısızlığı bir istisna değildir" - bu bazı durumlarda doğrudur, istisnanın istisnai olması gerekmez. Hata işleme içindir. Arayanın bir Boole sonucunu kontrol edeceğine dair bir garanti yoktur. Orada olan (başka bir şey yaparsa uygulamayı kapatma zamanı tarafından) bir istisna ele alınacaktır bir garanti.
09:17, TrueWill

Başvuruyu bulamıyorum ancak .NET'in bir istisnayı "bir yöntem ne yapacağını söylemedi" olarak tanımladığına inanıyorum. 1 amacı, Win32 deseni yerine, bir işlevin başarılı olup olmadığını kontrol etmesini istemesini gerektiren bir örüntüyü belirtmek için özel durumlar kullanmaktır.
noctonura

Ancak istisnalar sadece "bir sorunu göstermez". Ayrıca derlemek için zaman ve hafızaya mal olan bir dizi teşhis bilgisi içerir. Bunun en azından biraz önemli olmadığı açık durumlar var. Ama burada çok şey var. .NET, kontrol akışı için istisnalar kullanmaz (örneğin, Python'un StopIterationistisna kullanımı ile karşılaştırın ) ve bir nedeni vardır.
Robert Rossney

TryDoYöntem, model kaygan bir eğimdir. Bunu bilmeden önce, tüm çağrı yığını TryDoyöntemlerden oluşacaktır . Böyle bir karmaşadan kaçınmak için istisnalar icat edildi.
HappyNomad


2

Her iki istisnayı yeniden deneme seçeneğine sahip olmak veya istisna türünü açıkça ayarlamak isteyenler için şunu kullanın:

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

İptal işlemini destekleyen bir yönteme ihtiyacım vardı, bu sırada ben, ara hataları döndürmek için destek ekledim.

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

Bu Retryişlevi kullanabilirsiniz, 10 saniyelik bir gecikmeyle ancak iptal etmeden 3 kez tekrar deneyin.

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

Veya iptal edilmedikçe her beş saniyede bir ebediyen tekrar deneyin.

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

Tahmin edebileceğiniz gibi, kaynak Retrykodumda kullanmak istediğim farklı delgate türlerini destekleme işlevini aşırı yükledim.


2

Bu yöntem, belirli istisna türlerinde yeniden denemelere izin verir (diğerlerini hemen atar).

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

Örnek kullanım:

DoRetry(typeof(IOException), () => {
    using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(entryBytes, 0, entryBytes.Length);
    }
});

1

6 yıl sonra güncelleme: şimdi aşağıdaki yaklaşımın oldukça kötü olduğunu düşünüyorum. Yeniden deneme mantığı oluşturmak için Polly gibi bir kitaplık kullanmayı düşünmeliyiz.


Benim asyncyeniden deneme yönteminin uygulanması:

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

Anahtar noktalar: Ben kullandım .ConfigureAwait(false);ve Func<dynamic>bunun yerineFunc<T>


Bu soruya bir cevap sağlamaz. Lütfen sayfanın üst kısmındaki "Soru Sor" düğmesini kullanarak yanıtınızı yeni bir soru olarak göndermeyi ve ardından öğrendiklerinizi toplulukla paylaşmak için soruya kendi yanıtınızı göndermeyi düşünün.
elixenide

C # 5.0 ile codereview.stackexchange.com/q/55983/54000'den çok daha basit ama belki CansellactionToken enjekte edilmelidir.
SerG

Bu uygulama ile ilgili bir sorun var. Son yeniden denemeden sonra, vazgeçmeden hemen önce, Task.Delaysebepsiz çağrılır.
HappyNomad

@HappyNomad bu 6 yaşında bir cevap ve şimdi bir yeniden deneme mantığı oluşturmak için oldukça kötü bir yaklaşım olduğunu düşünüyorum :)) bildirim için teşekkürler. Cevabımı bu konuya göre güncelleyeceğim.
Cihan Uygun

0

Ya da biraz daha temiz yapmaya ne dersin ...

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

Sınırlar arasında geçiş yapmadıkça (başkalarının kullanabileceği bir kütüphane inşa etmek gibi) istisna atmaktan genellikle bir mekanizma olarak kaçınılması gerektiğine inanıyorum. DoSomething()Komut truebaşarılı olsaydı neden geri dönmüyor vefalse başka türlü ?

DÜZENLEME: Ve bu, başkalarının da önerdiği gibi bir işlev içinde kapsüllenebilir. Tek sorun, DoSomething()fonksiyonu kendiniz yazmıyorsanız


7
"Ben istisnalar atma genellikle bir mekanizma olarak kaçınılması gereken bir sınır olarak onları geçmesi inanıyorum" - Ben tamamen katılmıyorum. Arayanın yanlış (veya daha kötü, boş) dönüşünüzü kontrol ettiğini nereden biliyorsunuz? Kod neden başarısız oldu? Yanlış size başka bir şey söylemez. Arayanın arızayı yığının üzerinden geçmesi gerekiyorsa ne olur? Msdn.microsoft.com/en-us/library/ms229014.aspx adresini okuyun - bunlar kütüphaneler içindir, ancak dahili kod için de aynı mantıklıdır. Ve bir ekipte, diğer kişilerin kodunuzu çağırması muhtemeldir.
TrueWill

0

Yeniden denemek için bir yöntem benim parametre geçmek gerek vardı ve bir sonuç değeri var; bu yüzden bir ifadeye ihtiyacım var .. (LBushkin's bir esinlenerek) iş yapar bu sınıf kullanabilirsiniz böyle kullanabilirsiniz:

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. LBushkin'in çözümü bir deneme daha yapar = D


0

Kabul edilen cevaba aşağıdaki kodu eklerdim

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

Temel olarak yukarıdaki kod Retrysınıfı genel hale getirir, böylece yeniden denemek için yakalamak istediğiniz istisna türünü iletebilirsiniz.

Şimdi hemen hemen aynı şekilde kullanın, ancak istisna türünü belirtin

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

TRY döngüsü CATCH döngüsündeki kod istisnasız yürütülmüş olsa bile for döngüsü her zaman birkaç kez (retryCount değerinize göre) yürütülür. Ben for döngüsü wil üzerinde gidiş durmak böylece try döngüde yeniden denemek var eşit retryCount ayarlamak öneririz.
scre_www

@scre_www Yanlış olduğuna inanıyorum. Eğer actiono zaman atmaz Dodöner böylece breakuzak ing fordöngü.
HappyNomad

Her durumda, bu uygulama ile ilgili bir sorun var. Son yeniden denemeden sonra, vazgeçmeden hemen önce, Thread.Sleepsebepsiz çağrılır.
HappyNomad

0

Bu cevabın çok eski olduğunu biliyorum ama sadece bu konuda yorum yapmak istedim çünkü sayaçları kullanarak her ne olursa olsun, bunları kullanarak sorunları ile karşılaştım.

Yıllar içinde bence daha iyi bir yaklaşım benimsemiştim. Bu, "Konu" veya benzeri bir reaktif uzantı gibi bir tür olay toplama kullanmaktır. Bir deneme başarısız olduğunda, denemenin başarısız olduğunu söyleyen bir etkinlik yayınlarsınız ve toplayıcı işlevinin etkinliği yeniden zamanlamasını sağlarsınız. Bu, çağrının kendisini bir dizi yeniden deneme döngüsüyle kirletmeden yeniden deneme üzerinde çok daha fazla kontrol sahibi olmanızı sağlar. Ne de bir iplik ipliği uyuyor tek bir iplik bağlar.


0

C #, Java veya diğer dillerde basitleştirin:

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

ve kodunuzda çok basit kullanın:

 void simpleMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    //do some repetitive work
     }

    //some code    
    }

veya özyinelemeli yöntemlerde kullanabilirsiniz:

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

Ne yapıyorsun Request.BadRequest?
Danh

0

Yeniden deneme yardımcısı: hem iade edilebilir hem de geçersiz tür yeniden deneme içeren genel bir java uygulaması.

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

Kullanımı:

    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

İstisnaları toplayan ve iptali destekleyen bir async/ awaitsürüm.

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

Çünkü throw;sonsuz döngü sonlandırılır tek yolu, bu yöntem aslında ve istenen "kadar çalışmayın "N defa başarısız kadar deneyin" uyguluyor Nbaşarılı olana dek zamanlarda". Eğer bir break;veya daha return;sonra çağrı için ThingToTryDelegate();aksi takdirde asla başarısız olursa sürekli olarak çağrılacaktır gerekir. Ayrıca, ilk parametresinin TryNTimesadı olmadığı için bu derlenmez . -1.
BACON

-1

Burada yayınlanan cevaplara dayalı küçük bir sınıf yazdım. Umarım birine yardımcı olur: 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

Kabul edilen yanıtın zaman uyumsuz bir sürümünü uyguladım - ve güzel çalışıyor gibi görünüyor - herhangi bir yorum?


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

Ve basitçe şöyle deyin:

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

Thread.Sleep? Bir iş parçacığının engellenmesi, zaman uyumsuzluğun yararlarını ortadan kaldırır. Ayrıca Task DoAsync()sürüm tür bir argüman kabul gerektiğine eminim Func<Task>.
Theodor Zoulias
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.