วิธีที่ดีที่สุดในการเขียนตรรกะลองใหม่?


455

บางครั้งฉันจำเป็นต้องลองทำการผ่าตัดซ้ำหลายครั้งก่อนที่จะยอมแพ้ รหัสของฉันเหมือน:

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

ฉันต้องการเขียนใหม่ในฟังก์ชันลองใหม่ทั่วไปเช่น:

TryThreeTimes(DoSomething);

เป็นไปได้ใน C #? รหัสสำหรับTryThreeTimes()วิธีการคืออะไร?


1
รอบง่ายไม่เพียงพอ? ทำไมไม่ทำซ้ำและใช้ตรรกะหลาย ๆ ครั้ง?
Restuta

13
โดยส่วนตัวแล้วฉันจะระวังวิธีการช่วยเหลืออย่างมาก แน่นอนว่ามีความเป็นไปได้ที่จะใช้งาน lambdas แต่รูปแบบนั้นมีกลิ่นแรงมากดังนั้นการแนะนำตัวช่วย (ซึ่งบอกเป็นนัย ๆ ว่ามันถูกทำซ้ำบ่อย ๆ ) อยู่ในตัวของมันเองและมีความสงสัยอย่างมาก
Pavel Minaev

12
ในกรณีของฉัน DoSomething () ของฉันกำลังทำสิ่งต่าง ๆ บนเครื่องระยะไกลเช่นการลบไฟล์หรือพยายามตีพอร์ตเครือข่าย ในทั้งสองกรณีมีปัญหาการจับเวลาที่สำคัญเมื่อ DoSomething จะประสบความสำเร็จและเนื่องจากความห่างไกลไม่มีเหตุการณ์ที่ฉันสามารถฟังได้ ดังนั้นใช่มันส่งกลิ่น ยินดีต้อนรับข้อเสนอแนะ
noctonura

13
@PavelMinaev เหตุใดจึงต้องลองใช้คำแนะนำในการออกแบบโดยรวมที่ไม่ดี หากคุณเขียนโค้ดจำนวนมากที่เชื่อมต่อจุดรวมเข้าด้วยกันการใช้ความพยายามเป็นรูปแบบที่แน่นอนที่คุณควรพิจารณาใช้อย่างจริงจัง
bytedev

คำตอบ:


569

คำสั่งแบบครอบคลุมการจับที่เพียงแค่ลองโทรเดิมอาจเป็นอันตรายได้หากใช้เป็นกลไกการจัดการข้อยกเว้นทั่วไป ต้องบอกว่านี่เป็นเสื้อคลุมลองตีแลมด้าที่คุณสามารถใช้กับวิธีการใดก็ได้ ฉันเลือกที่จะคำนึงถึงจำนวนครั้งในการลองใหม่และการหมดเวลาลองอีกครั้งเป็นพารามิเตอร์เพื่อความยืดหยุ่นมากขึ้น:

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

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

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

ตอนนี้คุณสามารถใช้วิธีการยูทิลิตี้นี้เพื่อดำเนินการตรรกะลองใหม่:

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

หรือ:

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

หรือ:

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

หรือคุณอาจทำasyncเกินกำลัง


7
+1 โดยเฉพาะอย่างยิ่งสำหรับคำเตือนและการตรวจสอบข้อผิดพลาด ฉันจะรู้สึกสะดวกสบายมากขึ้นถ้าสิ่งนี้ผ่านไปในประเภทของข้อยกเว้นที่จะจับเป็นพารามิเตอร์ทั่วไป (โดยที่ T: Exception)
TrueWill

1
ฉันตั้งใจว่า "ลองใหม่" หมายถึงลองใหม่ แต่ก็ไม่ยากที่จะเปลี่ยนเป็น "พยายาม" ตราบใดที่ชื่อยังคงมีความหมาย มีโอกาสอื่น ๆ ในการปรับปรุงโค้ดเช่นการตรวจสอบการลองซ้ำเชิงลบหรือการหมดเวลาลบเช่น ฉันละเว้นสิ่งเหล่านี้ส่วนใหญ่เพื่อให้ง่ายตัวอย่าง ... แต่อีกครั้งในทางปฏิบัติเหล่านี้อาจจะเป็นการปรับปรุงที่ดีในการใช้งาน
LBushkin

40
เราใช้รูปแบบที่คล้ายกันสำหรับการเข้าถึงฐานข้อมูลของเราในแอป Biztalk ปริมาณสูง แต่มีการปรับปรุงสองประการ: เรามีบัญชีดำสำหรับข้อยกเว้นที่ไม่ควรลองใหม่และเราเก็บข้อยกเว้นแรกที่เกิดขึ้นและโยนทิ้งเมื่อการลองใหม่ล้มเหลว เหตุผลที่ข้อยกเว้นที่สองและต่อไปนี้มักจะแตกต่างจากข้อแรก ในกรณีนี้คุณซ่อนปัญหาเริ่มต้นเมื่อทำการสร้างใหม่ยกเว้นข้อยกเว้นสุดท้ายเท่านั้น
TToni

2
@Dexters เราส่งข้อยกเว้นใหม่พร้อมด้วยข้อยกเว้นดั้งเดิมเป็นข้อยกเว้นภายใน การติดตามสแต็กดั้งเดิมพร้อมใช้งานเป็นแอตทริบิวต์จากข้อยกเว้นภายใน
TToni

7
คุณสามารถลองใช้ไลบรารีโอเพนซอร์สเช่นพอลลี่เพื่อจัดการสิ่งนี้ได้ มีความยืดหยุ่นมากขึ้นสำหรับการรอระหว่างการลองใหม่และได้รับการตรวจสอบโดยคนอื่น ๆ ที่ใช้โครงการ ตัวอย่าง: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
ทอดด์ Meinershagen

222

คุณควรพยายามพอลลี่ มันเป็น. NET ห้องสมุดที่เขียนโดยฉันที่ช่วยให้นักพัฒนาสามารถแสดงนโยบายการจัดการข้อยกเว้นชั่วคราวเช่นลองใหม่ลองใหม่ตลอดกาลรอและลองใหม่หรือ Circuit Breaker อย่างคล่องแคล่ว

ตัวอย่าง

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

1
ผู้แทน OnRetry คืออะไรจริง ๆ ฉันคิดว่ามันเป็นสิ่งที่เราต้องดำเนินการเมื่อมีข้อยกเว้นเกิดขึ้น ดังนั้นเมื่อมีข้อยกเว้นเกิดขึ้นผู้แทน OnRetry จะโทรหาและหลังจากนั้นให้เรียกใช้งานผู้รับมอบสิทธิ์ มันเป็นอย่างนั้นเหรอ?
user6395764

61

นี่อาจเป็นความคิดที่ไม่ดี ประการแรกมันเป็นสัญลักษณ์ของคติพจน์สูงสุด "นิยามของความบ้ากำลังทำสิ่งเดียวกันสองครั้งและคาดหวังผลลัพธ์ที่แตกต่างกันในแต่ละครั้ง" ประการที่สองรูปแบบการเข้ารหัสนี้ไม่สามารถเขียนได้ดีกับตัวเอง ตัวอย่างเช่น:

สมมติว่าเลเยอร์ฮาร์ดแวร์เครือข่ายของคุณส่งแพ็กเก็ตอีกสามครั้งเมื่อเกิดความล้มเหลวรอพูดหนึ่งวินาทีระหว่างความล้มเหลว

ทีนี้สมมติว่าเลเยอร์ซอฟต์แวร์ส่งการแจ้งเตือนเกี่ยวกับความล้มเหลวสามครั้งในความล้มเหลวของแพ็กเก็ต

ตอนนี้สมมติว่าเลเยอร์การแจ้งเตือนเปิดใช้งานการแจ้งเตือนอีกสามครั้งเมื่อการส่งมอบการแจ้งเตือนล้มเหลว

ตอนนี้สมมติว่าชั้นการรายงานข้อผิดพลาดเปิดใช้งานเลเยอร์การแจ้งเตือนอีกสามครั้งเมื่อความล้มเหลวในการแจ้งเตือน

และตอนนี้สมมติว่าเว็บเซิร์ฟเวอร์เปิดใช้งานการรายงานข้อผิดพลาดอีกสามครั้งเมื่อเกิดข้อผิดพลาด

และตอนนี้สมมติว่าเว็บไคลเอ็นต์ส่งคำขออีกสามครั้งเมื่อได้รับข้อผิดพลาดจากเซิร์ฟเวอร์

ตอนนี้สมมติว่าบรรทัดบนสวิตช์เครือข่ายที่ควรจะกำหนดเส้นทางการแจ้งเตือนไปยังผู้ดูแลระบบไม่ได้เสียบปลั๊ก เมื่อใดที่ผู้ใช้เว็บไคลเอ็นต์ได้รับข้อความแสดงข้อผิดพลาดในที่สุด ฉันทำมันในอีกประมาณสิบสองนาทีต่อมา

เพื่อมิให้คุณคิดว่านี่เป็นเพียงตัวอย่างที่โง่เง่า: เราได้เห็นข้อบกพร่องนี้ในรหัสลูกค้าถึงแม้ว่าไกลยิ่งแย่กว่าที่ฉันเคยอธิบายไว้ที่นี่ ในรหัสลูกค้าโดยเฉพาะช่องว่างระหว่างเงื่อนไขข้อผิดพลาดที่เกิดขึ้นและในที่สุดก็ถูกรายงานไปยังผู้ใช้เป็นเวลาหลายสัปดาห์เพราะเลเยอร์จำนวนมากถูกลองใหม่โดยอัตโนมัติด้วยการรอ แค่คิดว่าจะเกิดอะไรขึ้นถ้ามีสิบลองใหม่แทนสามครั้งครั้ง

โดยปกติสิ่งที่ถูกต้องเกี่ยวกับเงื่อนไขข้อผิดพลาดจะรายงานทันทีและให้ผู้ใช้ตัดสินใจว่าจะทำอย่างไร หากผู้ใช้ต้องการสร้างนโยบายการลองใหม่โดยอัตโนมัติให้ผู้ใช้สร้างนโยบายนั้นในระดับที่เหมาะสมในซอฟต์แวร์นามธรรม


19
+1 เรย์มอนด์แชร์ตัวอย่างชีวิตจริงที่นี่blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
SolutionYogi

210
-1 คำแนะนำนี้ไม่มีประโยชน์สำหรับความล้มเหลวของเครือข่ายชั่วคราวที่พบโดยระบบประมวลผลแบบแบทช์อัตโนมัติ
nohat

15
ไม่แน่ใจว่านี่เป็นการพูดว่า "อย่าทำ" และตามด้วย "ทำ" คนส่วนใหญ่ถามคำถามนี้น่าจะเป็นคนที่ทำงานในซอฟต์แวร์ที่เป็นนามธรรม
Jim L

44
เมื่อคุณทำงานแบ็ตช์นานซึ่งใช้ทรัพยากรเครือข่ายเช่นบริการเว็บคุณไม่สามารถคาดหวังว่าเครือข่ายจะเชื่อถือได้ 100% จะมีการหมดเวลาเป็นครั้งคราวซ็อกเก็ตตัดการเชื่อมต่ออาจเป็นไปได้ผิดพลาดเส้นทางปลอมหรือขาดหายไปของเซิร์ฟเวอร์ที่เกิดขึ้นในขณะที่คุณกำลังใช้งาน ทางเลือกหนึ่งคือการล้มเหลว แต่นั่นอาจหมายถึงการเริ่มงานที่มีความยาวในภายหลัง อีกตัวเลือกหนึ่งคือลองใหม่สองสามครั้งด้วยความล่าช้าที่เหมาะสมเพื่อดูว่าเป็นปัญหาชั่วคราวหรือไม่ ฉันเห็นด้วยเกี่ยวกับการแต่งเพลงซึ่งคุณต้องระวัง .. แต่บางครั้งก็เป็นตัวเลือกที่ดีที่สุด
Erik Funkenbusch

18
ฉันคิดว่าคำพูดที่คุณใช้ตอนต้นคำตอบของคุณน่าสนใจ "การคาดหวังผลลัพธ์ที่แตกต่าง" เป็นสิ่งวิกลจริตเฉพาะเมื่อประสบการณ์ก่อนหน้านั้นให้ผลลัพธ์ที่เหมือนกันเป็นประจำ ในขณะที่ซอฟต์แวร์ถูกสร้างขึ้นตามคำมั่นสัญญาของความมั่นคงมีสถานการณ์ที่แน่นอนที่เราจำเป็นต้องมีปฏิสัมพันธ์กับกองกำลังที่ไม่น่าเชื่อถือนอกการควบคุมของเรา
Michael Richardson

49
public void TryThreeTimes(Action action)
{
    var tries = 3;
    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tries == 0)
                throw;
            Thread.Sleep(1000);
        }
    }
}

จากนั้นคุณจะโทร:

TryThreeTimes(DoSomething);

... หรืออีกวิธีหนึ่ง ...

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

ตัวเลือกที่ยืดหยุ่นมากขึ้น:

public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            action();
            break; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            Thread.Sleep(sleepPeriod);
        }
   }
}

ที่จะใช้เป็น:

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

รุ่นที่ทันสมัยกว่าพร้อมรองรับ async / คอย:

public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
    if (tryCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(tryCount));

    while (true) {
        try {
            await action();
            return; // success!
        } catch {
            if (--tryCount == 0)
                throw;
            await Task.Delay(sleepPeriod);
        }
   }
}

ที่จะใช้เป็น:

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

2
ควรเปลี่ยน if เป็น: --retryCount <= 0เนื่องจากสิ่งนี้จะดำเนินต่อไปตลอดไปหากคุณต้องการปิดการใช้งานการลองใหม่โดยตั้งค่าเป็น 0 ในทางเทคนิคแล้วคำว่าretryCountไม่ใช่ชื่อที่ดีจริง ๆ เพราะจะไม่ลองอีกครั้งหากคุณตั้งค่าไว้ที่ 1 มันเพื่อtryCountหรือใส่ - หลัง
Stefanvds

2
@saille ฉันเห็นด้วย อย่างไรก็ตาม OP (และคำตอบอื่น ๆ ) กำลังใช้งานThread.Sleepอยู่ ทางเลือกที่จะใช้ตัวนับหรือมีโอกาสมากขึ้นในปัจจุบันจะใช้เพื่อลองอีกครั้งด้วยasync Task.Delay
Drew Noakes

2
ฉันเพิ่มเวอร์ชัน async แล้ว
Drew Noakes

เพียงทำลายถ้าการดำเนินการreturns true? Func<bool>
Kiquenet

32

Transient Fault จัดการบล็อกโปรแกรมประยุกต์ให้เป็นคอลเลกชันของกลยุทธ์ขยายลองใหม่อีกครั้งรวมไปถึง:

  • ที่เพิ่มขึ้น
  • ช่วงเวลาคงที่
  • การถอยกลับแบบเอ็กซ์โปเนนเชียล

นอกจากนี้ยังมีชุดของกลยุทธ์การตรวจจับข้อผิดพลาดสำหรับบริการบนคลาวด์

สำหรับข้อมูลเพิ่มเติมดูบทนี้ของคู่มือนักพัฒนาซอฟต์แวร์

พร้อมใช้งานผ่านNuGet (ค้นหา ' topaz ')


1
น่าสนใจ คุณสามารถใช้สิ่งนี้นอก Windows Azure พูดในแอป Winforms ได้หรือไม่?
Matthew Lock

6
อย่างแน่นอน ใช้กลไกการลองใหม่หลักและจัดเตรียมกลยุทธ์การตรวจจับของคุณเอง เราตั้งใจแยกชิ้นส่วนเหล่านั้นออก ค้นหาแพ็คเกจแกนกลางได้ที่นี่: nuget.org/packages/TransientFaultHandling.Core
Grigori Melnik

2
นอกจากนี้โครงการนี้อยู่ภายใต้ Apache 2.0 และยอมรับการมีส่วนร่วมของชุมชน aka.ms/entlibopen
Grigori Melnik

1
@ Alex ชิ้นส่วนของมันทำให้มันกลายเป็นแพลตฟอร์ม
Grigori Melnik

2
นี้จะเลิกในขณะนี้และครั้งสุดท้ายที่ฉันใช้มันมันมีข้อบกพร่องบางอย่างที่ไกลที่สุดเท่าที่ฉันรู้ว่าไม่ได้และจะไม่ได้รับการแก้ไข: github.com/MicrosoftArchive/...
Ohad Schneider

15

อนุญาตให้ใช้งานฟังก์ชั่นและลองใหม่ข้อความ

public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
 Guard.IsNotNull(method, "method");            
 T retval = default(T);
 do
 {
   try
   {
     retval = method();
     return retval;
   }
   catch
   {
     onFailureAction();
      if (numRetries <= 0) throw; // improved to avoid silent failure
      Thread.Sleep(retryTimeout);
   }
} while (numRetries-- > 0);
  return retval;
}

RetryMethod to retvalเป็น True หรือmax retries?
Kiquenet

14

คุณอาจลองเพิ่มประเภทข้อยกเว้นที่คุณต้องการลองอีกครั้ง เช่นนี้เป็นข้อยกเว้นการหมดเวลาที่คุณต้องการลองอีกครั้งหรือไม่ ข้อยกเว้นฐานข้อมูล

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

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

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

คุณอาจทราบว่าตัวอย่างอื่น ๆ ทั้งหมดมีปัญหาคล้ายกันกับการทดสอบการลองใหม่ == 0 และลองอีกครั้งโดยไม่มีที่สิ้นสุดหรือไม่สามารถเพิ่มข้อยกเว้นเมื่อได้รับค่าลบ สลีป (-1000) จะล้มเหลวในบล็อก catch ด้านบน ขึ้นอยู่กับว่า 'โง่' คุณคาดหวังว่าคนจะเป็น แต่การเขียนโปรแกรมการป้องกันไม่เจ็บ


9
+1 แต่ทำไมไม่ลอง RetryForException <T> (... ) โดยที่ T: ข้อยกเว้นแล้วจับ (T e)? แค่ลองใช้และใช้งานได้อย่างสมบูรณ์
TrueWill

อย่างใดอย่างหนึ่งหรือที่นี่เนื่องจากฉันไม่จำเป็นต้องทำอะไรกับประเภทที่ให้ฉันคิดว่าพารามิเตอร์เก่าธรรมดาจะทำเคล็ดลับ
csharptest.net

@TrueWill catch (T ex) เห็นได้ชัดว่ามีข้อบกพร่องบางอย่างตามโพสต์นี้stackoverflow.com/questions/1577760/…
csharptest.net

3
อัปเดต: จริง ๆ แล้วการใช้งานที่ดีกว่าที่ฉันเคยใช้นั้นจะใช้ผู้รับมอบสิทธิ์ <ข้อยกเว้น> ซึ่งจะส่งกลับค่าจริงหากลองอีกครั้ง ซึ่งช่วยให้คุณใช้รหัสข้อผิดพลาดดั้งเดิมหรือคุณสมบัติอื่น ๆ ของข้อยกเว้นเพื่อตรวจสอบว่ามีความพยายามในการลองใหม่ ตัวอย่างเช่นรหัส HTTP 503
csharptest.net

1
"Sleep (-1000) จะล้มเหลวใน catch catch ข้างต้น" ... ใช้ TimeSpan และคุณจะไม่ได้รับปัญหานี้ Plus TimeSpan มีความยืดหยุ่นและอธิบายได้มากกว่า จากลายเซ็นของคุณของ "int retryTimeout" ฉันจะรู้ได้อย่างไรว่า retryTimeout คือ MS, วินาที, นาที, ปีหรือไม่ ;-)
bytedev

13

ฉันเป็นแฟนตัวยงของวิธีการสอบถามซ้ำและการขยายดังนั้นสองเซนต์ของฉันคือ:

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

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

7

จากการทำงานก่อนหน้านี้ฉันคิดถึงการปรับปรุงตรรกะลองใหม่ในสามวิธี:

  1. การระบุประเภทข้อยกเว้นที่จะจับ / ลองใหม่ นี่คือการปรับปรุงหลักเนื่องจากการลองใหม่เพื่อข้อยกเว้นใด ๆ เป็นเพียงความผิดปกติ
  2. ไม่ซ้อนการลองครั้งสุดท้ายในการลอง / จับการบรรลุประสิทธิภาพที่ดีขึ้นเล็กน้อย
  3. ทำให้มันเป็นActionวิธีการขยาย

    static class ActionExtensions
    {
      public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
      {
        if (action == null)
          throw new ArgumentNullException("action");
    
        while( retries-- > 0 )
        {
          try
          {
            action( );
            return;
          }
          catch (T)
          {
            Thread.Sleep( retryDelay );
          }
        }
    
        action( );
      }
    }

วิธีการนั้นสามารถเรียกใช้เช่นนั้น (วิธีการที่ไม่ระบุชื่อสามารถใช้เช่นกันแน่นอน):

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

1
มันยอดเยี่ยมมาก แต่โดยส่วนตัวฉันจะไม่เรียกมันว่า "retryTimeout" เพราะไม่ใช่การหมดเวลาจริงๆ 'RetryDelay' บางที
Holf

7

ทำให้มันง่ายด้วย C # 6.0

public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
    try
    {
        return action();
    }
    catch when (retryCount != 0)
    {
        await Task.Delay(retryInterval);
        return await Retry(action, retryInterval, --retryCount);
    }
}

2
ฉันอยากรู้อยากเห็นสิ่งนี้จะเกิดจำนวนเธรดที่บ้าที่มีจำนวนครั้งและการลองซ้ำสูงเนื่องจากการคืนค่าวิธีที่เหมือนกันหรือไม่
HuntK24

6

ใช้พอลลี่

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

นี่คือความพยายามทั่วไปที่ฉันใช้กับ Polly

public T Retry<T>(Func<T> action, int retryCount = 0)
{
    PolicyResult<T> policyResult = Policy
     .Handle<Exception>()
     .Retry(retryCount)
     .ExecuteAndCapture<T>(action);

    if (policyResult.Outcome == OutcomeType.Failure)
    {
        throw policyResult.FinalException;
    }

    return policyResult.Result;
}

ใช้มันแบบนี้

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

5

ปฏิบัติตามคำตอบของ LBushkin ในรูปแบบล่าสุด:

    public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }

                await task();
                return;
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }

    public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
    {
        var exceptions = new List<Exception>();
        for (int attempted = 0; attempted < maxAttemptCount; attempted++)
        {
            try
            {
                if (attempted > 0)
                {
                    await Task.Delay(retryInterval);
                }
                return await task();
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }
        throw new AggregateException(exceptions);
    }  

และใช้งาน:

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

ในขณะที่ฟังก์ชั่น[TaskFunction]สามารถเป็นได้ทั้งหรือเพียงแค่Task<T>Task


1
ขอบคุณเฟเบียน! นี่ควรจะถูกโหวตขึ้นไปจนถึงอันดับสูงสุด!
JamesHoux

1
@ MarkLauter คำตอบสั้น ๆ คือใช่ ;-)
Fabian Bigler

4

ฉันจะใช้สิ่งนี้:

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

ฉันจะไม่ใช้ข้อยกเว้นวิธีที่พวกเขาใช้ในตัวอย่างอื่น ๆ ดูเหมือนว่าสำหรับฉันหากเราคาดหวังว่าจะมีวิธีการที่ไม่ประสบความสำเร็จความล้มเหลวของมันก็ไม่ใช่ข้อยกเว้น ดังนั้นวิธีการที่ฉันโทรควรกลับจริงถ้ามันประสบความสำเร็จและเท็จถ้ามันล้มเหลว

ทำไมมันFunc<bool, bool>และไม่ใช่แค่Func<bool>? ดังนั้นถ้าฉันต้องการวิธีที่จะสามารถยกเว้นความล้มเหลวฉันมีวิธีแจ้งว่านี่เป็นความพยายามครั้งสุดท้าย

ดังนั้นฉันอาจใช้กับรหัสเช่น:

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

หรือ

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

หากผ่านพารามิเตอร์ที่เมธอดไม่ได้ใช้พิสูจน์ให้เห็นว่าอึดอัดใจมันเป็นเรื่องยากที่จะใช้โอเวอร์โหลดของRetryสิ่งนั้นก็ใช้เวลาFunc<bool>เช่นกัน


1
+1 เพื่อหลีกเลี่ยงข้อยกเว้น แม้ว่าฉันจะทำโมฆะลองอีกครั้ง (... ) แล้วขว้างบางอย่าง? บูลีนส่งคืนและ / หรือโค้ดส่งคืนมักถูกมองข้ามบ่อยเกินไป
csharptest.net

1
"หากเราคาดหวังความเป็นไปได้ที่วิธีหนึ่งจะไม่ประสบความสำเร็จความล้มเหลวของมันไม่ได้เป็นข้อยกเว้น" - ในขณะที่มันเป็นจริงในบางกรณีข้อยกเว้นไม่จำเป็นต้องมีความหมายพิเศษ มันมีไว้สำหรับจัดการข้อผิดพลาด ไม่มีการรับประกันว่าผู้โทรจะตรวจสอบผลบูลีน มีคือรับประกันว่าข้อยกเว้นจะถูกจัดการ (โดยปิดรันไทม์ลงแอพลิเคชันถ้าไม่มีอะไรอื่นไม่)
TrueWill

ฉันไม่พบการอ้างอิง แต่ฉันเชื่อว่า. NET กำหนดข้อยกเว้นเป็น "วิธีการไม่ได้ทำในสิ่งที่กล่าวว่าจะทำได้" 1 วัตถุประสงค์คือการใช้ข้อยกเว้นเพื่อระบุปัญหามากกว่ารูปแบบ Win32 ที่ต้องการให้ผู้โทรตรวจสอบค่าส่งคืนหากฟังก์ชันนั้นสำเร็จหรือไม่
noctonura

แต่ข้อยกเว้นไม่เพียง "บ่งบอกถึงปัญหา" นอกจากนี้ยังมีข้อมูลการวินิจฉัยจำนวนมากซึ่งทำให้เสียเวลาและหน่วยความจำในการรวบรวม มีสถานการณ์ที่ชัดเจนซึ่งไม่สำคัญเลยแม้แต่น้อย แต่มีหลายอย่างที่มันทำ .NET ไม่ได้ใช้ข้อยกเว้นสำหรับโฟลว์การควบคุม (เปรียบเทียบพูดด้วยการใช้StopIterationข้อยกเว้นของ Python ) และมีเหตุผล
Robert Rossney

TryDoรูปแบบวิธีการเป็นลื่นลาด ก่อนที่คุณจะรู้ว่าสแตกการโทรทั้งหมดของคุณจะประกอบด้วยTryDoวิธีการต่างๆ ข้อยกเว้นถูกคิดค้นเพื่อหลีกเลี่ยงความยุ่งเหยิงดังกล่าว
HappyNomad


2

สำหรับผู้ที่ต้องการมีทั้งตัวเลือกให้ลองใหม่ในข้อยกเว้นใด ๆ หรือตั้งค่าประเภทข้อยกเว้นอย่างชัดเจนให้ใช้สิ่งนี้:

public class RetryManager 
{
    public void Do(Action action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        Try<object, Exception>(() => {
            action();
            return null;
        }, interval, retries);
    }

    public T Do<T>(Func<T> action, 
                    TimeSpan interval, 
                    int retries = 3)
    {
        return Try<T, Exception>(
              action
            , interval
            , retries);
    }

    public T Do<E, T>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        return Try<T, E>(
              action
            , interval
            , retries);
    }

    public void Do<E>(Action action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        Try<object, E>(() => {
            action();
            return null;
        }, interval, retries);
    }

    private T Try<T, E>(Func<T> action, 
                       TimeSpan interval, 
                       int retries = 3) where E : Exception
    {
        var exceptions = new List<E>();

        for (int retry = 0; retry < retries; retry++)
        {
            try
            {
                if (retry > 0)
                    Thread.Sleep(interval);
                return action();
            }
            catch (E ex)
            {
                exceptions.Add(ex);
            }
        }

        throw new AggregateException(exceptions);
    }
}

2

ฉันต้องการวิธีการที่สนับสนุนการยกเลิกในขณะที่ฉันอยู่ที่นั่นฉันได้เพิ่มการสนับสนุนสำหรับการส่งคืนความล้มเหลวระดับกลาง

public static class ThreadUtils
{
    public static RetryResult Retry(
        Action target,
        CancellationToken cancellationToken,
        int timeout = 5000,
        int retries = 0)
    {
        CheckRetryParameters(timeout, retries)
        var failures = new List<Exception>();
        while(!cancellationToken.IsCancellationRequested)
        {
            try
            {
                target();
                return new RetryResult(failures);
            }
            catch (Exception ex)
            {
                failures.Add(ex);
            }

            if (retries > 0)
            {
                retries--;
                if (retries == 0)
                {
                    throw new AggregateException(
                     "Retry limit reached, see InnerExceptions for details.",
                     failures);
                }
            }

            if (cancellationToken.WaitHandle.WaitOne(timeout))
            {
                break;
            }
        }

        failures.Add(new OperationCancelledException(
            "The Retry Operation was cancelled."));
        throw new AggregateException("Retry was cancelled.", failures);
    }

    private static void CheckRetryParameters(int timeout, int retries)
    {
        if (timeout < 1)
        {
            throw new ArgumentOutOfRangeException(...
        }

        if (retries < 0)
        {
            throw new ArgumentOutOfRangeException(...

        }
    }

    public class RetryResult : IEnumerable<Exception>
    {
        private readonly IEnumerable<Exception> failureExceptions;
        private readonly int failureCount;

         protected internal RetryResult(
             ICollection<Exception> failureExceptions)
         {
             this.failureExceptions = failureExceptions;
             this.failureCount = failureExceptions.Count;
         }
    }

    public int FailureCount
    {
        get { return this.failureCount; }
    }

    public IEnumerator<Exception> GetEnumerator()
    {
        return this.failureExceptions.GetEnumerator();
    }

    System.Collections.IEnumerator 
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

คุณสามารถใช้Retryฟังก์ชั่นเช่นนี้ลองอีกครั้ง 3 ครั้งด้วยความล่าช้า 10 วินาที แต่ไม่มีการยกเลิก

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        CancellationToken.None,
        10000,
        3);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // oops, 3 retries wasn't enough.
}

หรือลองอีกครั้งในทุก ๆ ห้าวินาทีเว้นแต่จะถูกยกเลิก

try
{
    var result = ThreadUtils.Retry(
        SomeAction, 
        someTokenSource.Token);

    // it worked
    result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
   // operation was cancelled before success.
}

อย่างที่คุณสามารถคาดเดาได้ในซอร์สโค้ดของฉันฉันได้ใช้Retryฟังก์ชันมากเกินไปเพื่อรองรับเดลเกตชนิดต่าง ๆ ที่ฉันต้องการใช้


2

วิธีนี้อนุญาตให้ลองใหม่ในบางประเภทของข้อยกเว้น (ส่งต่อผู้อื่นทันที)

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

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

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

ตัวอย่างการใช้งาน:

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

1

อัปเดตหลังจาก 6 ปี:ตอนนี้ฉันพิจารณาแล้วว่าวิธีการด้านล่างค่อนข้างแย่ ในการสร้างตรรกะลองใหม่เราควรพิจารณาใช้ไลบรารีอย่างพอลลี่


asyncการนำวิธีการลองใหม่ไปใช้ของฉัน:

public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
    {
        var exceptions = new List<Exception>();

        for (int retry = 0; retry < retryCount; retry++)
        {
            try
            {
                return await action().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }

            await Task.Delay(retryInterval).ConfigureAwait(false);
        }
        throw new AggregateException(exceptions);
    }

ประเด็นสำคัญ: ฉันใช้ .ConfigureAwait(false);และFunc<dynamic>แทนFunc<T>


สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม โปรดลองโพสต์คำตอบของคุณเป็นคำถามใหม่โดยใช้ปุ่ม "ถามคำถาม" ที่ด้านบนของหน้าจากนั้นโพสต์คำตอบของคุณเองในคำถามเพื่อแบ่งปันสิ่งที่คุณเรียนรู้กับชุมชน
elixenide

ง่ายกว่าด้วย C # 5.0 มากกว่า codereview.stackexchange.com/q/55983/54000 แต่อาจจะฉีด CansellactionToken
SerG

มีปัญหากับการใช้งานนี้ หลังจากการลองใหม่ครั้งสุดท้ายก่อนจะTask.Delayถูกเรียกขึ้นมาโดยไม่มีเหตุผล
HappyNomad

@HappyNomad นี่เป็นคำตอบอายุ 6 ปีและตอนนี้ฉันคิดว่ามันเป็นวิธีที่ไม่ดีในการสร้างตรรกะลองใหม่ :)) ขอบคุณสำหรับการแจ้งเตือน ฉันจะอัปเดตคำตอบของฉันตามการพิจารณานั้น
Cihan Uygun

0

หรือวิธีการเกี่ยวกับการทำมันค่อนข้าง neater ....

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

ฉันเชื่อว่าการหลีกเลี่ยงข้อยกเว้นการขว้างปาเป็นกลไกเว้นแต่ว่าคุณผ่านพวกเขาไประหว่างเขตแดน (เช่นการสร้างห้องสมุดที่คนอื่นสามารถใช้) ทำไมไม่เพียงแค่DoSomething()ส่งคืนคำสั่งtrueถ้ามันประสบความสำเร็จและfalseอื่น ๆ ?

แก้ไข:และสิ่งนี้สามารถห่อหุ้มภายในฟังก์ชั่นเหมือนที่คนอื่น ๆ ได้แนะนำเช่นกัน ปัญหาเดียวคือถ้าคุณไม่ได้เขียนDoSomething()ฟังก์ชั่นด้วยตัวเอง


7
"ฉันเชื่อว่าการขว้างข้อยกเว้นโดยทั่วไปควรหลีกเลี่ยงเป็นกลไกเว้นแต่คุณผ่านพวกเขาไปในเขตแดน" - ฉันไม่เห็นด้วยอย่างสมบูรณ์ คุณจะรู้ได้อย่างไรว่าผู้โทรเข้ามาตรวจสอบการคืนของคุณผิดพลาด ทำไมรหัสจึงล้มเหลว เท็จบอกคุณไม่มีอะไรอื่น เกิดอะไรขึ้นถ้าผู้โทรต้องผ่านความล้มเหลวขึ้นสแต็ค? อ่านmsdn.microsoft.com/en-us/library/ms229014.aspx - สิ่งนี้มีไว้สำหรับไลบรารี แต่มันก็สมเหตุสมผลสำหรับโค้ดภายใน และในทีมคนอื่น ๆ มักจะเรียกรหัสของคุณ
TrueWill

0

ฉันต้องการส่งพารามิเตอร์ไปยังวิธีของฉันเพื่อลองอีกครั้งและมีค่าผลลัพธ์ ดังนั้นฉันต้องการนิพจน์ .. ฉันสร้างคลาสนี้ขึ้นมาซึ่งทำงาน (มันเป็นแรงบันดาลใจให้กับ LBushkin's one) คุณสามารถใช้มันได้:

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 ลองอีกครั้ง = D


0

ฉันจะเพิ่มรหัสต่อไปนี้ในคำตอบที่ยอมรับ

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

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

            for (int retry = 0; retry < retryCount; retry++)
            {
                try
                {
                    return action();
                }
                catch (TException ex) //Usage of the exception type
                {
                    exceptions.Add(ex);
                    Thread.Sleep(retryInterval);
                }
            }

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

โดยทั่วไปโค้ดด้านบนกำลังสร้างRetryคลาสทั่วไปเพื่อให้คุณสามารถส่งชนิดของข้อยกเว้นที่คุณต้องการตรวจสอบเพื่อลองใหม่

ตอนนี้ใช้มันเกือบจะในลักษณะเดียวกัน แต่การระบุประเภทข้อยกเว้น

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

การวนซ้ำจะดำเนินการสองสามครั้ง (ตามจำนวน retryCount ของคุณ) แม้ว่ารหัสในการวนรอบ TRY CATCH จะถูกดำเนินการโดยไม่มีข้อยกเว้น ฉันอยากจะแนะนำให้ตั้ง retryCount เท่ากับ retry var ในลองวนซ้ำดังนั้นสำหรับวนรอบจะหยุดมากกว่า
scre_www

@scre_www ฉันเชื่อว่าคุณเข้าใจผิด ถ้าactionไม่โยนก็ให้Doผลตอบแทนbreakจากการforวนซ้ำ
HappyNomad

ไม่ว่าในกรณีใดมีปัญหากับการใช้งานนี้ หลังจากการลองใหม่ครั้งสุดท้ายก่อนจะThread.Sleepถูกเรียกขึ้นมาโดยไม่มีเหตุผล
HappyNomad

0

ฉันรู้ว่าคำตอบนี้เก่ามาก แต่ฉันแค่ต้องการแสดงความคิดเห็นเกี่ยวกับเรื่องนี้เพราะฉันมีปัญหาในการใช้สิ่งเหล่านี้ในขณะที่ทำอะไรก็ตามที่มีเคาน์เตอร์

ในช่วงหลายปีที่ผ่านมาฉันได้ตัดสินวิธีที่ดีกว่าที่ฉันคิด นั่นคือการใช้การรวมเหตุการณ์บางอย่างเช่นส่วนขยายปฏิกิริยา "เรื่อง" หรือสิ่งที่คล้าย เมื่อการลองล้มเหลวคุณเพียงแค่เผยแพร่เหตุการณ์ที่บอกว่าการลองล้มเหลวและมีฟังก์ชันการรวมตัวจัดกำหนดการเหตุการณ์อีกครั้ง วิธีนี้ช่วยให้คุณสามารถควบคุมการลองใหม่ได้มากขึ้นโดยไม่ทำให้เกิดการเรียกตัวเองด้วยการวนซ้ำของการลองซ้ำและสิ่งที่ไม่ คุณไม่ได้ผูกหัวข้อเดียวกับกลุ่มของ sleeps


0

ทำง่ายใน C #, Java หรือภาษาอื่น ๆ :

  internal class ShouldRetryHandler {
    private static int RETRIES_MAX_NUMBER = 3;
    private static int numberTryes;

    public static bool shouldRetry() {
        var statusRetry = false;

        if (numberTryes< RETRIES_MAX_NUMBER) {
            numberTryes++;
            statusRetry = true;
            //log msg -> 'retry number' + numberTryes

        }

        else {
            statusRetry = false;
            //log msg -> 'reached retry number limit' 
        }

        return statusRetry;
    }
}

และใช้ในรหัสของคุณง่ายมาก:

 void simpleMethod(){
    //some code

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

    //some code    
    }

หรือคุณสามารถใช้มันในวิธีแบบเรียกซ้ำ:

void recursiveMethod(){
    //some code

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

    //some code    
    }

0
int retries = 3;
while (true)
{
    try
    {
        //Do Somthing
        break;
    }
    catch (Exception ex)
    {
        if (--retries == 0)
            return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
        else
            System.Threading.Thread.Sleep(100);
    }

สิ่งที่คุณทำกับRequest.BadRequest?
Danh

0

ผู้ช่วยทดลองอีกครั้ง: การใช้งานจาวาทั่วไปที่มีทั้งความพยายามส่งคืนและโมฆะประเภท

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

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

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

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

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

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

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

  }

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

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

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

    return supplier.get();
  }

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

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

การใช้งาน:

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

0

นี่คือasync/ awaitรุ่นที่รวมข้อยกเว้นและรองรับการยกเลิก

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

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

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

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

-1
public delegate void ThingToTryDeletage();

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

            if( --N == 0) throw;
          else Thread.Sleep(time);          
      }
}

เนื่องจากthrow;เป็นวิธีเดียวที่จะสิ้นสุดการวนซ้ำไม่สิ้นสุดวิธีนี้จึงใช้งานจริง "ลองจนกว่าจะล้มเหลว N ครั้ง" และไม่ใช่ที่ต้องการ "ลองจนถึงNเวลาจนกว่าจะสำเร็จ" คุณต้องการbreak;หรือreturn;หลังจากการโทรไปThingToTryDelegate();มิฉะนั้นจะถูกเรียกอย่างต่อเนื่องหากไม่เคยล้มเหลว นอกจากนี้สิ่งนี้จะไม่รวบรวมเพราะพารามิเตอร์แรกของTryNTimesไม่มีชื่อ -1
BACON

-1

ฉันเขียนชั้นเรียนขนาดเล็กตามคำตอบที่โพสต์ไว้ที่นี่ หวังว่ามันจะช่วยให้ใครบางคน: https://github.com/natenho/resiliency

using System;
using System.Threading;

/// <summary>
/// Classe utilitária para suporte a resiliência
/// </summary>
public sealed class Resiliency
{
    /// <summary>
    /// Define o valor padrão de número de tentativas
    /// </summary>
    public static int DefaultRetryCount { get; set; }

    /// <summary>
    /// Define o valor padrão (em segundos) de tempo de espera entre tentativas
    /// </summary>
    public static int DefaultRetryTimeout { get; set; }

    /// <summary>
    /// Inicia a parte estática da resiliência, com os valores padrões
    /// </summary>
    static Resiliency()
    {
        DefaultRetryCount = 3;
        DefaultRetryTimeout = 0;
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes  quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
    public static void Try(Action action)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
    {
        Try<Exception>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, retryCount, retryTimeout, tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
    {
        Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action) where TException : Exception
    {
        Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    public static void Try<TException>(Action action, int retryCount) where TException : Exception
    {
        Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount"></param>
    /// <param name="retryTimeout"></param>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
    {
        Try<TException>(action, retryCount, retryTimeout, null);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
    public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
    }

    /// <summary>
    /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
    /// </summary>
    /// <param name="action">Ação a ser realizada</param>
    /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
    /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
    /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
    /// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
    public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        while (retryCount-- > 0)
        {
            try
            {
                action();
                return;
            }
            catch (TException ex)
            {
                //Executa o manipulador de exception
                if (tryHandler != null)
                {
                    var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
                    tryHandler(callback);
                    //A propriedade que aborta pode ser alterada pelo cliente
                    if (callback.AbortRetry)
                        throw;
                }

                //Aguarda o tempo especificado antes de tentar novamente
                Thread.Sleep(retryTimeout);
            }
        }

        //Na última tentativa, qualquer exception será lançada de volta ao chamador
        action();
    }

}

/// <summary>
/// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
/// </summary>
public class ResiliencyTryHandler<TException> where TException : Exception
{
    #region Properties

    /// <summary>
    /// Opção para abortar o ciclo de tentativas
    /// </summary>
    public bool AbortRetry { get; set; }

    /// <summary>
    /// <see cref="Exception"/> a ser tratada
    /// </summary>
    public TException Exception { get; private set; }

    /// <summary>
    /// Identifca o número da tentativa atual
    /// </summary>
    public int CurrentTry { get; private set; }

    #endregion

    #region Constructors

    /// <summary>
    /// Instancia um manipulador de tentativa. É utilizado internamente
    /// por <see cref="Resiliency"/> para permitir que o cliente altere o
    /// comportamento do ciclo de tentativas
    /// </summary>
    public ResiliencyTryHandler(TException exception, int currentTry)
    {
        Exception = exception;
        CurrentTry = currentTry;
    }

    #endregion

}

-1

ฉันใช้คำตอบที่ยอมรับแล้วในเวอร์ชัน async - และดูเหมือนว่าจะใช้งานได้ดี - มีความคิดเห็นอะไรบ้าง?


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

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

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

และเรียกมันว่าเป็นอย่างนี้:

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

Thread.Sleep? การบล็อกเธรดคัดค้านผลประโยชน์ของอะซิงโครนัส นอกจากนี้ผมค่อนข้างแน่ใจว่ารุ่นควรจะยอมรับข้อโต้แย้งของพิมพ์Task DoAsync() Func<Task>
Theodor Zoulias
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.