Cara terbersih untuk menulis coba lagi logika?

455

Kadang-kadang saya harus mencoba lagi operasi beberapa kali sebelum menyerah. Kode saya seperti:

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

Saya ingin menulis ulang ini dalam fungsi coba lagi umum seperti:

TryThreeTimes(DoSomething);

Apakah mungkin dalam C #? Apa yang akan menjadi kode untuk TryThreeTimes()metode ini?

noctonura
sumber
1
Siklus sederhana tidak cukup? Mengapa tidak mengulangi dan mengeksekusi logika untuk beberapa kali?
Restuta
13
Secara pribadi, saya akan sangat waspada terhadap metode penolong semacam itu. Memang mungkin untuk menerapkan menggunakan lambdas, tetapi polanya sendiri sangat berbau, jadi memperkenalkan pembantu untuk itu (yang menyiratkan bahwa itu sering diulang) sangat mencurigakan, dan sangat mengisyaratkan desain keseluruhan yang buruk.
Pavel Minaev
12
Dalam kasus saya, DoSomething () saya melakukan hal-hal pada mesin jarak jauh seperti menghapus file, atau mencoba menekan port jaringan. Dalam kedua kasus, ada masalah waktu utama ketika DoSomething akan berhasil dan karena keterpencilan, tidak ada acara yang bisa saya dengarkan. Jadi ya, baunya. Saran diterima.
noctonura
13
@ PavelMinaev mengapa menggunakan retries hint pada desain keseluruhan yang buruk? Jika Anda menulis banyak kode yang menghubungkan titik integrasi maka menggunakan retries jelas merupakan pola yang harus Anda pertimbangkan untuk digunakan.
bytedev

Jawaban:

569

Pernyataan blanket catch yang hanya mencoba ulang panggilan yang sama bisa berbahaya jika digunakan sebagai mekanisme penanganan pengecualian umum. Karena itu, inilah pembungkus coba ulang berbasis lambda yang dapat Anda gunakan dengan metode apa pun. Saya memilih untuk memperhitungkan jumlah percobaan dan batas waktu coba ulang sebagai parameter untuk fleksibilitas yang lebih banyak:

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

Anda sekarang dapat menggunakan metode utilitas ini untuk melakukan coba lagi logika:

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

atau:

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

atau:

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

Atau Anda bahkan bisa membuat asynckelebihan.

LBushkin
sumber
7
+1, terutama untuk peringatan dan pengecekan kesalahan. Saya akan lebih nyaman jika ini lulus dalam jenis pengecualian untuk menangkap sebagai parameter umum (di mana T: Pengecualian), meskipun.
TrueWill
1
Maksud saya bahwa "retries" sebenarnya berarti retries. Tetapi tidak terlalu sulit untuk mengubahnya menjadi "mencoba". Selama nama itu tetap bermakna. Ada peluang lain untuk meningkatkan kode, seperti memeriksa coba ulang negatif, atau batas waktu negatif - misalnya. Saya mengabaikan ini sebagian besar untuk menjaga contoh sederhana ... tapi sekali lagi, dalam praktiknya ini mungkin akan menjadi tambahan yang baik untuk implementasi.
LBushkin
40
Kami menggunakan pola yang sama untuk akses DB kami di Aplikasi Biztalk volume tinggi, tetapi dengan dua perbaikan: Kami memiliki daftar hitam untuk pengecualian yang tidak boleh dicoba lagi dan kami menyimpan pengecualian pertama yang terjadi dan melemparkannya ketika coba lagi gagal. Alasannya adalah bahwa pengecualian kedua dan berikut sering berbeda dari yang pertama. Dalam hal ini Anda menyembunyikan masalah awal saat hanya memikirkan kembali pengecualian terakhir.
TToni
2
@ Teks Kami melemparkan pengecualian baru dengan pengecualian asli sebagai pengecualian dalam. Jejak tumpukan asli tersedia sebagai atribut dari pengecualian dalam.
TToni
7
Anda juga dapat mencoba menggunakan pustaka sumber terbuka seperti Polly untuk menangani ini. Ada jauh lebih banyak fleksibilitas untuk menunggu antar percobaan dan telah divalidasi oleh banyak orang lain yang telah menggunakan proyek. Contoh: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen
222

Anda harus mencoba Polly . Ini adalah .NET library yang ditulis oleh saya yang memungkinkan pengembang untuk mengekspresikan kebijakan penanganan pengecualian sementara seperti Retry, Retry Forever, Wait and Retry atau Circuit Breaker dengan lancar.

Contoh

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());
Michael Wolfenden
sumber
1
Apa sebenarnya delegasi OnRetry? Saya menganggap itu yang perlu kita lakukan ketika pengecualian terjadi. Jadi, ketika pengecualian terjadi, delegasi OnRetry akan menelepon dan kemudian Menjalankan delegasi. Benarkah begitu?
user6395764
61

Ini mungkin ide yang buruk. Pertama, simbol dari pepatah "definisi kegilaan adalah melakukan hal yang sama dua kali dan mengharapkan hasil yang berbeda setiap kali". Kedua, pola pengkodean ini tidak bisa dikomposisi dengan baik. Sebagai contoh:

Misalkan lapisan perangkat keras jaringan Anda mengirim ulang sebuah paket tiga kali kegagalan, menunggu, katakanlah, satu detik di antara kegagalan.

Sekarang anggaplah lapisan perangkat lunak mengirim ulang pemberitahuan tentang kegagalan tiga kali pada kegagalan paket.

Sekarang anggaplah lapisan pemberitahuan mengaktifkan kembali pemberitahuan tiga kali pada kegagalan pengiriman pemberitahuan.

Sekarang anggaplah lapisan pelaporan kesalahan mengaktifkan kembali lapisan pemberitahuan tiga kali pada kegagalan pemberitahuan.

Dan sekarang misalkan server web mengaktifkan kembali pelaporan kesalahan tiga kali pada kegagalan kesalahan.

Dan sekarang anggap klien web mengirim ulang permintaan tiga kali setelah mendapatkan kesalahan dari server.

Sekarang anggaplah garis pada switch jaringan yang seharusnya untuk merutekan notifikasi ke administrator dicabut. Kapan pengguna klien web akhirnya mendapatkan pesan kesalahan mereka? Saya membuatnya sekitar dua belas menit kemudian.

Jangan sampai Anda menganggap ini hanya contoh konyol: kami telah melihat bug ini dalam kode pelanggan, meskipun jauh, jauh lebih buruk daripada yang saya jelaskan di sini. Dalam kode pelanggan tertentu, kesenjangan antara kondisi kesalahan terjadi dan akhirnya dilaporkan kepada pengguna adalah beberapa minggu karena begitu banyak lapisan secara otomatis mencoba kembali dengan menunggu. Bayangkan saja apa yang akan terjadi jika ada sepuluh percobaan bukan tiga .

Biasanya hal yang benar untuk dilakukan dengan kondisi kesalahan adalah melaporkannya segera dan membiarkan pengguna memutuskan apa yang harus dilakukan. Jika pengguna ingin membuat kebijakan coba ulang otomatis, biarkan mereka membuat kebijakan itu pada tingkat yang sesuai dalam abstraksi perangkat lunak.

Eric Lippert
sumber
19
+1. Raymond membagikan contoh kehidupan nyata di sini, blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi
210
-1 Saran ini tidak berguna untuk kegagalan jaringan sementara yang dihadapi oleh sistem pemrosesan batch otomatis.
nohat
15
Tidak yakin apakah ini mengatakan "Jangan lakukan itu" diikuti dengan "lakukan itu". Sebagian besar orang yang mengajukan pertanyaan ini mungkin adalah orang-orang yang bekerja dalam abstraksi perangkat lunak.
Jim L
44
Ketika Anda memiliki pekerjaan batch lama yang menggunakan sumber daya jaringan, seperti layanan web, Anda tidak dapat mengharapkan jaringan menjadi 100% dapat diandalkan. Akan ada timeout sesekali, soket terputus, bahkan mungkin gangguan routing palsu atau pemadaman server yang terjadi saat Anda menggunakannya. Salah satu opsi adalah gagal, tetapi itu mungkin berarti memulai kembali pekerjaan yang panjang nanti. Pilihan lain adalah mencoba lagi beberapa kali dengan penundaan yang sesuai untuk melihat apakah itu masalah sementara, lalu gagal. Saya setuju tentang komposisi, yang harus Anda waspadai ... tapi kadang-kadang itu pilihan terbaik.
Erik Funkenbusch
18
Saya pikir kutipan yang Anda gunakan di awal jawaban Anda menarik. "Mengharapkan hasil yang berbeda" hanyalah kegilaan jika pengalaman sebelumnya secara teratur memberi Anda hasil yang sama. Sementara perangkat lunak dibangun berdasarkan janji konsistensi, pasti ada keadaan di mana kita diminta untuk berinteraksi dengan kekuatan yang tidak dapat diandalkan di luar kendali kita.
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);
        }
    }
}

Maka Anda akan menelepon:

TryThreeTimes(DoSomething);

... atau sebagai alternatif ...

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

Opsi yang lebih fleksibel:

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

Untuk digunakan sebagai:

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

Versi yang lebih modern dengan dukungan untuk async / menunggu:

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

Untuk digunakan sebagai:

await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
Drew Noakes
sumber
2
lebih baik ubah if ke: --retryCount <= 0karena ini akan berlangsung selamanya jika Anda ingin menonaktifkan retryCountcoba lagi dengan menetapkannya ke 0. Secara teknis istilah ini bukan nama yang benar-benar baik, karena itu tidak akan coba lagi jika Anda menetapkannya ke 1. baik ganti nama ke tryCountatau meletakkan - di belakang.
Stefanvds
2
@aille, saya setuju. Namun OP (dan semua jawaban lainnya) menggunakan Thread.Sleep. Alternatifnya adalah menggunakan timer, atau lebih mungkin saat ini asyncuntuk mencoba lagi, dengan Task.Delay.
Drew Noakes
2
Saya telah menambahkan versi async.
Drew Noakes
Hanya istirahat jika tindakan returns true? Func<bool>
Kiquenet
32

The Transient Kesalahan Penanganan Aplikasi Blokir menyediakan koleksi extensible strategi coba lagi termasuk:

  • Tambahan
  • Interval tetap
  • Pengunduran secara eksponensial

Ini juga mencakup kumpulan strategi deteksi kesalahan untuk layanan berbasis cloud.

Untuk informasi lebih lanjut lihat bab Panduan Pengembang ini.

Tersedia melalui NuGet (mencari ' topaz ').

Grigori Melnik
sumber
1
Menarik. Bisakah Anda menggunakan ini di luar Windows Azure, katakanlah di aplikasi Winforms?
Matthew Lock
6
Benar. Gunakan mekanisme coba ulang inti dan berikan strategi deteksi Anda sendiri. Kami sengaja memisahkannya. Temukan paket inti nuget di sini: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik
2
Selain itu, proyek ini sekarang berada di bawah Apache 2.0 dan menerima kontribusi komunitas. aka.ms/entlibopen
Grigori Melnik
1
@Alex. Potongan-potongan itu membuatnya menjadi platform.
Grigori Melnik
2
Ini sekarang sudah usang, dan terakhir saya menggunakannya mengandung beberapa bug yang sejauh yang saya tahu tidak, dan tidak akan pernah diperbaiki: github.com/MicrosoftArchive/… .
Ohad Schneider
15

Mengizinkan fungsi dan coba lagi pesan

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;
}
Brian
sumber
RetryMethod to retval Benar, ataumax retries?
Kiquenet
14

Anda mungkin juga mempertimbangkan untuk menambahkan tipe pengecualian yang ingin Anda coba kembali. Misalnya apakah ini pengecualian waktu tunggu yang ingin Anda coba lagi? Pengecualian basis data?

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

Anda mungkin juga mencatat bahwa semua contoh lain memiliki masalah yang sama dengan pengujian untuk coba lagi == 0 dan coba lagi tak terhingga atau gagal untuk meningkatkan pengecualian ketika diberi nilai negatif. Juga Sleep (-1000) akan gagal di blok tangkap di atas. Bergantung pada seberapa 'konyol' Anda mengharapkan orang, tetapi pemrograman defensif tidak pernah sakit.

csharptest.net
sumber
9
+1, tetapi mengapa tidak melakukan RetryForException <T> (...) di mana T: Pengecualian, lalu menangkap (T e)? Baru saja mencobanya dan berfungsi dengan baik.
TrueWill
Entah atau di sini karena saya tidak perlu melakukan apa pun dengan Type asalkan saya pikir parameter lama biasa akan melakukan trik.
csharptest.net
@TrueWill rupanya catch (T ex) memiliki beberapa bug menurut posting ini stackoverflow.com/questions/1577760/...
csharptest.net
3
Pembaruan: Sebenarnya implementasi yang lebih baik yang saya gunakan menggunakan delegasi Predicate <Exception> yang mengembalikan true jika coba lagi sesuai. Ini memungkinkan Anda untuk menggunakan kode kesalahan asli atau properti lain dari pengecualian untuk menentukan apakah coba lagi berlaku. Misalnya kode HTTP 503.
csharptest.net
1
"Juga Tidur (-1000) akan gagal di blok tangkap di atas" ... gunakan TimeSpan dan Anda tidak akan mendapatkan masalah ini. Plus TimeSpan jauh lebih fleksibel dan deskriptif diri. Dari tanda tangan Anda "int retryTimeout" bagaimana saya tahu jika retryTimeout adalah MS, detik, menit, tahun ?? ;-)
bytedev
13

Saya penggemar metode rekursi dan ekstensi, jadi inilah dua sen saya:

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

        InvokeWithRetries(@this, --numberOfRetries);
    }
}
Martin RL
sumber
7

Membangun pada karya sebelumnya, saya berpikir tentang meningkatkan logika coba lagi dengan tiga cara:

  1. Menentukan jenis pengecualian apa yang akan ditangkap / coba lagi. Ini adalah peningkatan utama karena mencoba kembali untuk pengecualian apa pun benar-benar salah.
  2. Tidak bersarang percobaan terakhir dalam mencoba / menangkap, mencapai kinerja yang sedikit lebih baik
  3. Menjadikannya Actionmetode ekstensi

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

Metode ini kemudian dapat dipanggil seperti itu (metode anonim tentu saja dapat digunakan):

new Action( AMethodThatMightThrowIntermittentException )
  .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
Igor Pashchuk
sumber
1
Ini luar biasa. Tetapi secara pribadi saya tidak akan menyebutnya 'retryTimeout' karena ini bukan Timeout. 'RetryDelay', mungkin?
Holf
7

Sederhanakan dengan 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);
    }
}
Anders Skovborg
sumber
2
Saya agak penasaran, apakah ini akan menghasilkan jumlah utas yang gila dengan interval dan coba lagi yang tinggi karena mengembalikan metode yang sama?
HuntK24
6

Gunakan Polly

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

Ini adalah retry-generic yang saya gunakan dengan 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;
}

Gunakan seperti ini

var result = Retry(() => MyFunction()), 3);
Erik Bergstedt
sumber
5

Menerapkan jawaban LBushkin dengan cara terbaru:

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

dan untuk menggunakannya:

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

sedangkan fungsinya [TaskFunction]dapat berupa Task<T>atau hanya Task.

Fabian Bigler
sumber
1
Terima kasih, Fabian! Ini harus diputuskan sampai ke puncak!
JamesHoux
1
@ MarkLauter jawaban singkatnya adalah ya. ;-)
Fabian Bigler
4

Saya akan menerapkan ini:

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

Saya tidak akan menggunakan pengecualian seperti yang mereka gunakan dalam contoh lain. Sepertinya saya bahwa jika kita mengharapkan kemungkinan bahwa suatu metode tidak akan berhasil, kegagalannya tidak terkecuali. Jadi metode yang saya panggil harus mengembalikan true jika berhasil, dan false jika gagal.

Mengapa ini Func<bool, bool>dan bukan hanya Func<bool>? Jadi kalau saya mau metode untuk dapat melemparkan pengecualian pada kegagalan, saya punya cara untuk memberitahukan bahwa ini adalah percobaan terakhir.

Jadi saya mungkin menggunakannya dengan kode seperti:

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

atau

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

Jika melewati parameter yang metode ini tidak gunakan terbukti canggung, itu sepele untuk menerapkan kelebihan Retryyang hanya membutuhkan waktu Func<bool>juga.

Robert Rossney
sumber
1
+1 untuk menghindari pengecualian. Meskipun saya akan melakukan void Retry (...) dan melempar sesuatu? Pengembalian Boolean dan / atau kode pengembalian terlalu sering diabaikan.
csharptest.net
1
"jika kita mengharapkan kemungkinan bahwa suatu metode tidak akan berhasil, kegagalannya bukanlah pengecualian" - sementara itu benar dalam beberapa kasus, pengecualian tidak harus menyiratkan pengecualian. Ini untuk penanganan kesalahan. Tidak ada jaminan bahwa penelepon akan memeriksa hasil Boolean. Ada adalah jaminan bahwa pengecualian akan ditangani (oleh runtime mematikan aplikasi jika tidak ada lagi tidak).
TrueWill
Saya tidak dapat menemukan referensi tetapi saya percaya. NET mendefinisikan Pengecualian sebagai "metode tidak melakukan apa yang dikatakannya akan melakukan". Tujuan 1 adalah menggunakan pengecualian untuk menunjukkan masalah daripada pola Win32 yang mengharuskan pemanggil untuk memeriksa nilai kembali jika fungsi berhasil atau tidak.
noctonura
Tetapi pengecualian tidak hanya "menunjukkan masalah." Mereka juga menyertakan banyak informasi diagnostik yang memerlukan waktu dan memori untuk dikompilasi. Jelas ada situasi di mana itu tidak menjadi masalah. Tetapi ada banyak hal yang terjadi. .NET tidak menggunakan pengecualian untuk aliran kontrol (bandingkan, katakanlah, dengan penggunaan Python StopIterationpengecualian), dan ada alasannya.
Robert Rossney
The TryDoPola metode adalah lereng licin. Sebelum Anda menyadarinya, seluruh tumpukan panggilan Anda akan terdiri dari TryDometode. Pengecualian diciptakan untuk menghindari kekacauan seperti itu.
HappyNomad
2

Backoff eksponensial adalah strategi coba lagi yang baik daripada hanya mencoba x beberapa kali. Anda dapat menggunakan perpustakaan seperti Polly untuk mengimplementasikannya.

utsavized
sumber
2

Bagi mereka yang ingin memiliki kedua opsi untuk mencoba kembali pada pengecualian apa pun atau secara eksplisit mengatur jenis pengecualian, gunakan ini:

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);
    }
}
mrogunlana
sumber
2

Saya membutuhkan metode yang mendukung pembatalan, sementara saya melakukannya, saya menambahkan dukungan untuk mengembalikan kegagalan perantara.

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

Anda dapat menggunakan Retryfungsi seperti ini, coba lagi 3 kali dengan jeda 10 detik tetapi tanpa pembatalan.

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

Atau, coba lagi selamanya setiap lima detik, kecuali dibatalkan.

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

Seperti yang bisa Anda tebak, dalam kode sumber saya, saya telah membebani Retryfungsi untuk mendukung berbagai jenis delgate yang ingin saya gunakan.

Jodrell
sumber
2

Metode ini memungkinkan percobaan ulang pada jenis pengecualian tertentu (langsung melempar orang lain).

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

Contoh penggunaan:

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

Pembaruan setelah 6 tahun: sekarang saya menganggap bahwa pendekatan di bawah ini sangat buruk. Untuk membuat logika coba lagi kita harus mempertimbangkan untuk menggunakan perpustakaan seperti Polly.


asyncImplementasi saya dari metode coba lagi:

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

Poin-poin penting: Saya menggunakan .ConfigureAwait(false);dan Func<dynamic>sebagai gantinyaFunc<T>

Cihan Uygun
sumber
Ini tidak memberikan jawaban untuk pertanyaan itu. Harap pertimbangkan untuk memposting jawaban Anda sebagai pertanyaan baru, menggunakan tombol "Ajukan Pertanyaan" di bagian atas halaman, kemudian posting jawaban Anda sendiri untuk pertanyaan tersebut untuk membagikan apa yang Anda pelajari dengan komunitas.
elixenide
Jauh lebih sederhana dengan C # 5.0 daripada codereview.stackexchange.com/q/55983/54000 tapi mungkin CansellactionToken harus disuntikkan.
SerG
Ada masalah dengan implementasi ini. Setelah percobaan ulang terakhir, tepat sebelum menyerah, Task.Delaydipanggil tanpa alasan.
HappyNomad
@HappyNomad ini adalah jawaban 6 tahun dan sekarang saya menganggap itu pendekatan yang cukup buruk untuk membuat coba lagi logika :)) terima kasih atas pemberitahuan. Saya akan memperbarui jawaban saya sesuai dengan pertimbangan itu.
Cihan Uygun
0

Atau bagaimana melakukannya dengan sedikit lebih rapi ....

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

Saya percaya melempar pengecualian pada umumnya harus dihindari sebagai mekanisme kecuali Anda melewati mereka di antara batas (seperti membangun perpustakaan yang dapat digunakan orang lain). Mengapa tidak mengembalikan DoSomething()perintah saja truejika berhasil dan falsesebaliknya?

EDIT: Dan ini dapat dienkapsulasi di dalam fungsi seperti yang disarankan orang lain juga. Satu-satunya masalah adalah jika Anda tidak menulis DoSomething()fungsinya sendiri

mikrofon
sumber
7
"Saya percaya melempar pengecualian pada umumnya harus dihindari sebagai mekanisme kecuali jika Anda melewati mereka di antara batas" - Saya benar-benar tidak setuju. Bagaimana Anda tahu penelepon memeriksa kembali palsu Anda (atau lebih buruk, nol)? MENGAPA kode gagal? Salah tidak memberitahumu hal lain. Bagaimana jika penelepon harus meneruskan kegagalannya ke tumpukan? Baca msdn.microsoft.com/en-us/library/ms229014.aspx - ini untuk pustaka, tetapi sama masuk akalnya dengan kode internal. Dan di tim, orang lain cenderung memanggil kode Anda.
TrueWill
0

Saya harus melewati beberapa parameter ke metode saya untuk mencoba lagi, dan memiliki nilai hasil; jadi saya butuh ekspresi .. Saya membangun kelas ini yang berfungsi (terinspirasi oleh LBushkin) Anda dapat menggunakannya seperti ini:

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. solusi LBushkin melakukan satu lagi coba lagi = D

Paolo Sanchi
sumber
0

Saya akan menambahkan kode berikut ke jawaban yang diterima

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

Pada dasarnya kode di atas adalah membuat Retry kelas generik sehingga Anda dapat melewati jenis pengecualian yang ingin Anda tangkap untuk mencoba lagi.

Sekarang gunakan itu hampir dengan cara yang sama tetapi menentukan jenis pengecualian

Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
Juan M. Elosegui
sumber
Untuk loop akan selalu mengeksekusi beberapa kali (berdasarkan retryCount Anda) bahkan jika kode di loop TRY CATCH dieksekusi tanpa kecuali. Saya akan menyarankan untuk mengatur retryCount sama dengan coba lagi var di loop coba, jadi untuk loop wil akan berhenti melewatinya.
scre_www
@ scre_www Saya yakin Anda salah. Jika actiontidak melempar maka Dokembali dengan demikian breakmenjauh dari forloop.
HappyNomad
Bagaimanapun, ada masalah dengan implementasi ini. Setelah percobaan ulang terakhir, tepat sebelum menyerah, Thread.Sleepdipanggil tanpa alasan.
HappyNomad
0

Saya tahu jawaban ini sudah sangat lama tetapi saya hanya ingin mengomentari ini karena saya telah mengalami masalah saat menggunakan ini, lakukan, pernyataan apa pun dengan penghitung.

Selama bertahun-tahun, saya telah memutuskan pendekatan yang lebih baik. Yaitu dengan menggunakan semacam agregasi acara seperti ekstensi reaktif "Subjek" atau sejenisnya. Ketika percobaan gagal, Anda cukup mempublikasikan sebuah acara yang mengatakan percobaan gagal, dan minta fungsi agregator menjadwalkan ulang acara tersebut. Ini memungkinkan Anda mengontrol lebih jauh atas coba ulang tanpa mencemari panggilan itu sendiri dengan sekelompok loop coba lagi dan apa yang tidak. Anda juga tidak mengikat satu utas dengan seikat benang tidur.

Brandon
sumber
0

Lakukan secara sederhana dalam C #, Java atau bahasa lain:

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

dan menggunakannya dalam kode Anda sangat sederhana:

 void simpleMethod(){
    //some code

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

    //some code    
    }

atau Anda dapat menggunakannya dalam metode rekursif:

void recursiveMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }

    //some code    
    }
Choletski
sumber
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);
    }
Bhaskar
sumber
Apa yang Anda lakukan dengan Request.BadRequest?
Danh
0

Retry helper: implementasi java umum yang berisi retries tipe yang dapat dikembalikan dan tidak berlaku.

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

Pemakaian:

    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();
    }
divya jain
sumber
0

Berikut adalah async/ awaitversi yang mengagregasi pengecualian dan mendukung pembatalan.

/// <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 );
}
HappyNomad
sumber
-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);          
      }
}
Mark P Neyer
sumber
Karena satu throw;-satunya cara loop infinite diakhiri, metode ini sebenarnya menerapkan "coba sampai gagal N kali" dan bukan yang diinginkan "coba hingga Nwaktu sampai berhasil". Anda memerlukan break;atau return;setelah panggilan ke ThingToTryDelegate();sebaliknya panggilan akan dipanggil terus menerus jika tidak pernah gagal. Juga, ini tidak akan dikompilasi karena parameter pertama TryNTimestidak memiliki nama. -1.
BACON
-1

Saya telah menulis sebuah kelas kecil berdasarkan jawaban yang diposting di sini. Semoga ini akan membantu seseorang: 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

}
natenho
sumber
-1

Saya telah mengimplementasikan versi async dari jawaban yang diterima seperti itu - dan tampaknya berfungsi dengan baik - ada komentar?


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

Dan, sebut saja seperti ini:

var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);
Kerneels Roos
sumber
Thread.Sleep? Memblokir utas meniadakan manfaat asinkron. Saya juga cukup yakin bahwa Task DoAsync()versi tersebut harus menerima argumen tipe Func<Task>.
Theodor Zoulias