Cách sạch nhất để viết logic thử lại?


455

Thỉnh thoảng tôi có nhu cầu thử lại một hoạt động nhiều lần trước khi từ bỏ. Mã của tôi giống như:

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

Tôi muốn viết lại cái này trong một hàm thử lại chung như:

TryThreeTimes(DoSomething);

Có thể trong C #? Mã cho TryThreeTimes()phương thức là gì?


1
Một chu kỳ đơn giản là không đủ? Tại sao không lặp đi lặp lại và thực hiện logic nhiều lần?
Restuta

13
Cá nhân, tôi sẽ cực kỳ cảnh giác với bất kỳ phương pháp trợ giúp như vậy. Chắc chắn có thể thực hiện bằng cách sử dụng lambdas, nhưng bản thân mẫu này cực kỳ nặng mùi, do đó, việc giới thiệu một người trợ giúp cho nó (ngụ ý rằng nó thường được lặp đi lặp lại) là rất đáng ngờ, và gợi ý mạnh mẽ về thiết kế tổng thể xấu.
Pavel Minaev

12
Trong trường hợp của tôi, các DoS Something () của tôi đang thực hiện công cụ trên các máy từ xa như xóa tệp hoặc cố gắng truy cập vào cổng mạng. Trong cả hai trường hợp, có những vấn đề lớn về thời gian khi DoS Something sẽ thành công và vì sự xa xôi, không có sự kiện nào tôi có thể lắng nghe. Vì vậy, yeah, mùi của nó. Gợi ý chào mừng.
noctonura

13
@PavelMinaev tại sao sẽ sử dụng gợi ý thử lại ở thiết kế tổng thể xấu? Nếu bạn viết nhiều mã kết nối các điểm tích hợp thì sử dụng thử lại chắc chắn là một mẫu bạn nên nghiêm túc xem xét sử dụng.
bytedev

Câu trả lời:


569

Các tuyên bố bắt chăn chỉ đơn giản thử lại cùng một cuộc gọi có thể gây nguy hiểm nếu được sử dụng như một cơ chế xử lý ngoại lệ chung. Phải nói rằng, đây là một trình bao bọc thử lại dựa trên lambda mà bạn có thể sử dụng với bất kỳ phương pháp nào. Tôi đã chọn tính hệ số lần thử lại và thời gian thử lại là các tham số để linh hoạt hơn một chút:

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

Bây giờ bạn có thể sử dụng phương thức tiện ích này để thực hiện thử lại logic:

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

hoặc là:

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

hoặc là:

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

Hoặc thậm chí bạn có thể làm asyncquá tải.


7
+1, đặc biệt là để cảnh báo và kiểm tra lỗi. Tôi sẽ thoải mái hơn nếu điều này được chuyển qua loại ngoại lệ để bắt như một tham số chung (trong đó T: Exception), mặc dù.
TrueWill

1
Đó là ý định của tôi rằng "thử lại" thực sự có nghĩa là thử lại. Nhưng không quá khó để thay đổi nó thành "cố gắng". Miễn là tên được giữ ý nghĩa. Có nhiều cơ hội khác để cải thiện mã, chẳng hạn như kiểm tra các lần thử lại tiêu cực hoặc thời gian chờ tiêu cực - chẳng hạn. Tôi đã bỏ qua những điều này chủ yếu để giữ cho ví dụ đơn giản ... nhưng một lần nữa, trong thực tế, đây có thể là những cải tiến tốt cho việc thực hiện.
LBushkin

40
Chúng tôi sử dụng một mẫu tương tự cho quyền truy cập DB của chúng tôi trong Ứng dụng Biztalk số lượng lớn, nhưng với hai cải tiến: Chúng tôi có danh sách đen cho các trường hợp ngoại lệ không nên thử lại và chúng tôi lưu trữ ngoại lệ đầu tiên xảy ra và ném khi thử lại cuối cùng thất bại. Lý do là ngoại lệ thứ hai và sau thường khác với ngoại lệ thứ nhất. Trong trường hợp đó, bạn ẩn vấn đề ban đầu khi chỉ nghĩ lại ngoại lệ cuối cùng.
TToni

2
@Dexters Chúng tôi ném một ngoại lệ mới với ngoại lệ ban đầu là ngoại lệ bên trong. Theo dõi ngăn xếp ban đầu có sẵn như là thuộc tính từ các ngoại lệ bên trong.
TToni

7
Bạn cũng có thể thử sử dụng một thư viện mã nguồn mở như Polly để xử lý việc này. Có nhiều sự linh hoạt hơn cho việc chờ đợi giữa các lần thử lại và nó đã được xác nhận bởi nhiều người khác đã sử dụng dự án. Ví dụ: Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Todd Meinershagen

222

Bạn nên thử Polly . Đó là một thư viện .NET được viết bởi tôi cho phép các nhà phát triển thể hiện các chính sách xử lý ngoại lệ nhất thời như Retry, Retry Forever, Wait và Retry hoặc Circuit Breaker một cách trôi chảy.

Thí dụ

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

1
Đại biểu OnR tem thực sự là gì? Tôi cho rằng đó là những gì chúng ta cần thực hiện khi xảy ra ngoại lệ. Vì vậy, khi xảy ra ngoại lệ, đại biểu OnR tem sẽ gọi và sau đó Thực thi ủy quyền. Có phải vậy không
user6395764

61

Đây có thể là một ý tưởng tồi. Đầu tiên, nó là biểu tượng của câu châm ngôn "định nghĩa điên rồ đang làm điều tương tự hai lần và mong đợi kết quả khác nhau mỗi lần". Thứ hai, mẫu mã hóa này không hợp với chính nó. Ví dụ:

Giả sử lớp phần cứng mạng của bạn gửi lại một gói ba lần khi thất bại, chờ đợi, giả sử, một giây giữa các lần hỏng.

Bây giờ giả sử lớp phần mềm gửi lại thông báo về lỗi ba lần về lỗi gói.

Bây giờ, giả sử lớp thông báo kích hoạt lại thông báo ba lần khi lỗi gửi thông báo.

Bây giờ, giả sử lớp báo cáo lỗi kích hoạt lại lớp thông báo ba lần khi lỗi thông báo.

Và bây giờ giả sử máy chủ web kích hoạt lại báo cáo lỗi ba lần về lỗi lỗi.

Và bây giờ, giả sử máy khách web gửi lại yêu cầu ba lần khi nhận được lỗi từ máy chủ.

Bây giờ, giả sử đường dây trên mạng chuyển mạch được cho là định tuyến thông báo tới quản trị viên được rút ra. Khi nào thì người dùng web client cuối cùng nhận được thông báo lỗi của họ? Tôi làm nó vào khoảng mười hai phút sau.

Vì sợ rằng bạn nghĩ đây chỉ là một ví dụ ngớ ngẩn: chúng tôi đã thấy lỗi này trong mã khách hàng, mặc dù xa, tệ hơn nhiều so với tôi đã mô tả ở đây. Trong mã khách hàng cụ thể, khoảng cách giữa tình trạng lỗi xảy ra và cuối cùng nó được báo cáo cho người dùng là vài tuần vì rất nhiều lớp đã tự động thử lại với sự chờ đợi. Chỉ cần tưởng tượng những gì sẽ xảy ra nếu có mười lần thử lại thay vì ba lần .

Thông thường, điều đúng đắn cần làm với một điều kiện lỗi là báo cáo ngay lập tức và để người dùng quyết định phải làm gì. Nếu người dùng muốn tạo chính sách thử lại tự động, hãy để họ tạo chính sách đó ở mức phù hợp trong phần trừu tượng hóa.


19
+1. Raymond chia sẻ một ví dụ thực tế ở đây, blog.msdn.com/oldnewthing/archive/2005/11/07/361807.aspx
SolutionYogi

210
-1 Lời khuyên này là vô ích đối với các lỗi mạng tạm thời gặp phải bởi các hệ thống xử lý hàng loạt tự động.
gì vào

15
Không chắc chắn nếu điều này nói "Đừng làm điều đó" theo sau là "làm điều đó". Hầu hết những người hỏi câu hỏi này có lẽ là những người làm việc trong phần mềm trừu tượng.
Jim L

44
Khi bạn có các công việc hàng loạt chạy dài sử dụng tài nguyên mạng, chẳng hạn như dịch vụ web, bạn không thể mong đợi mạng đáng tin cậy 100%. Thỉnh thoảng sẽ có thời gian chờ, ngắt kết nối ổ cắm, thậm chí có thể xảy ra sự cố định tuyến giả hoặc sự cố ngừng máy chủ xảy ra trong khi bạn đang sử dụng nó. Một lựa chọn là thất bại, nhưng điều đó có thể có nghĩa là bắt đầu lại một công việc dài sau đó. Một lựa chọn khác là thử lại một vài lần với độ trễ phù hợp để xem đó có phải là sự cố tạm thời không, sau đó thất bại. Tôi đồng ý về thành phần, mà bạn phải biết .. nhưng đôi khi nó là sự lựa chọn tốt nhất.
Erik Funkenbusch

18
Tôi nghĩ rằng trích dẫn bạn sử dụng ở đầu câu trả lời của bạn là thú vị. "Mong đợi kết quả khác nhau" chỉ là sự điên rồ nếu kinh nghiệm trước đó thường xuyên cho bạn kết quả tương tự. Mặc dù phần mềm được xây dựng dựa trên lời hứa về tính nhất quán, nhưng chắc chắn có những trường hợp chúng tôi bắt buộc phải tương tác với các lực lượng không đáng tin cậy ngoài tầm kiểm soát của chúng tôi.
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);
        }
    }
}

Sau đó, bạn sẽ gọi:

TryThreeTimes(DoSomething);

...Hay cách khác...

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

Một lựa chọn linh hoạt hơn:

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

Được sử dụng như:

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

Một phiên bản hiện đại hơn với sự hỗ trợ cho async / await:

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

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

Được sử dụng như:

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

2
tốt nhất là thay đổi if thành: --retryCount <= 0bởi vì điều này sẽ diễn ra mãi mãi nếu bạn muốn vô hiệu hóa thử lại bằng cách đặt nó thành 0. Về mặt kỹ thuật, thuật ngữ retryCountnày không phải là một tên thực sự tốt, bởi vì nó sẽ không thử lại nếu bạn đặt nó thành 1. đổi tên nó để tryCounthoặc đặt - phía sau.
Stefanvds

2
@saille Tôi đồng ý. Tuy nhiên, OP (và tất cả các câu trả lời khác) đang sử dụng Thread.Sleep. Các lựa chọn thay thế là sử dụng bộ hẹn giờ, hoặc ngày nay nhiều khả năng sử dụng asyncđể thử lại với Task.Delay.
vẽ Noakes

2
Tôi đã thêm một phiên bản không đồng bộ.
vẽ Noakes

Chỉ phá vỡ nếu hành động returns true? Func<bool>
Kiquenet

32

Các thoáng lỗi Xử lý Application Block cung cấp một bộ sưu tập mở rộng các chiến lược thử lại bao gồm:

  • Tăng dần
  • Khoảng thời gian cố định
  • Back-off mũ

Nó cũng bao gồm một tập hợp các chiến lược phát hiện lỗi cho các dịch vụ dựa trên đám mây.

Để biết thêm thông tin, hãy xem chương này của Hướng dẫn dành cho nhà phát triển.

Có sẵn thông qua NuGet (tìm kiếm ' topaz ').


1
Hấp dẫn. Bạn có thể sử dụng điều này bên ngoài Windows Azure, trong ứng dụng Winforms không?
Khóa Matthew

6
Chắc chắn rồi. Sử dụng cơ chế thử lại cốt lõi và cung cấp các chiến lược phát hiện của riêng bạn. Chúng tôi cố tình tách những người đó. Tìm gói nuget
Grigori Melnik

2
Ngoài ra, dự án hiện thuộc Apache 2.0 và chấp nhận đóng góp của cộng đồng. aka.ms/entlibopen
Grigori Melnik

1
@Alex. Các mảnh của nó đang làm cho nó vào nền tảng.
Grigori Melnik

2
Điều này hiện không được chấp nhận và cuối cùng tôi đã sử dụng nó, nó có một số lỗi mà theo như tôi không biết, và sẽ không bao giờ được sửa: github.com/MicrosoftArchive/ .
Ohad Schneider

15

Cho phép các chức năng và thử lại tin nhắn

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

RetryMethod để retval là True, haymax retries?
Kiquenet

14

Bạn cũng có thể xem xét thêm loại ngoại lệ bạn muốn thử lại. Chẳng hạn, đây có phải là ngoại lệ hết thời gian bạn muốn thử lại không? Một ngoại lệ cơ sở dữ liệu?

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

Bạn cũng có thể lưu ý rằng tất cả các ví dụ khác có vấn đề tương tự với việc kiểm tra thử lại == 0 và thử lại vô cùng hoặc không đưa ra ngoại lệ khi đưa ra giá trị âm. Ngoài ra Ngủ (-1000) sẽ thất bại trong các khối bắt ở trên. Phụ thuộc vào mức độ 'ngớ ngẩn' mà bạn mong đợi mọi người sẽ có nhưng lập trình phòng thủ không bao giờ bị tổn thương.


9
+1, nhưng tại sao không thực hiện RetryForException <T> (...) trong đó T: Ngoại lệ, sau đó bắt (T e)? Chỉ cần thử nó và nó hoạt động hoàn hảo.
TrueWill

Hoặc hoặc ở đây vì tôi không cần phải làm bất cứ điều gì với Loại được cung cấp, tôi nghĩ rằng một tham số cũ đơn giản sẽ thực hiện thủ thuật.
csharptest.net

@TrueWill rõ ràng là bắt (T ex) có một số lỗi theo bài đăng này stackoverflow.com/questions/1577760/
Kẻ

3
Cập nhật: Trên thực tế, việc triển khai tốt hơn mà tôi đang sử dụng cần một đại biểu Dự đoán <Ngoại lệ> trả về giá trị đúng nếu thử lại là phù hợp. Điều này cho phép bạn sử dụng mã lỗi gốc hoặc các thuộc tính khác của ngoại lệ để xác định xem có thử lại hay không. Ví dụ mã HTTP 503.
csharptest.net

1
"Ngoài ra Ngủ (-1000) sẽ thất bại trong các khối bắt ở trên" ... sử dụng TimeSpan và bạn sẽ không gặp phải vấn đề này. Cộng với TimeSpan linh hoạt và tự mô tả hơn nhiều. Từ chữ ký "int retryTimeout" của bạn, làm thế nào để tôi biết nếu retryTimeout là MS, giây, phút, năm ?? ;-)
bytedev

13

Tôi là người hâm mộ các phương pháp đệ quy và mở rộng, vì vậy đây là hai xu của tôi:

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

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

7

Dựa trên công việc trước đó, tôi đã nghĩ đến việc tăng cường logic thử lại theo ba cách:

  1. Chỉ định loại ngoại lệ để bắt / thử lại. Đây là cải tiến chính vì thử lại cho bất kỳ ngoại lệ nào chỉ là sai.
  2. Không lồng lần thử cuối cùng trong một lần thử / bắt, đạt được hiệu suất tốt hơn một chút
  3. Làm cho nó trở thành một Actionphương thức mở rộng

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

Phương thức sau đó có thể được gọi như vậy (tất nhiên cũng có thể sử dụng phương thức ẩn danh):

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

1
Thật tuyệt vời. Nhưng cá nhân tôi sẽ không gọi nó là 'retryTimeout' vì nó không thực sự là Hết giờ. 'RetryDelay', có lẽ?
Holf

7

Giữ cho nó đơn giản với 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
Tôi hơi tò mò, liệu điều này có sinh ra một số lượng lớn các luồng với số lần thử lại cao và khoảng thời gian vì trả về cùng một phương thức đang chờ đợi không?
HuntK24

6

Sử dụng Polly

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

Đây là một thử lại chung chung tôi sử dụng với 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;
}

Sử dụng nó như thế này

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

5

Đã thực hiện câu trả lời của LBushkin theo cách mới nhất:

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

và sử dụng nó:

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

trong khi chức năng [TaskFunction]có thể Task<T>hoặc chỉ Task.


1
Cảm ơn bạn, Fabian! Điều này nên được nâng cấp tất cả các cách lên hàng đầu!
JamesHoux

1
@MarkLauter câu trả lời ngắn gọn là có. ;-)
Fabian Bigler

4

Tôi sẽ thực hiện điều này:

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

Tôi sẽ không sử dụng ngoại lệ theo cách chúng được sử dụng trong các ví dụ khác. Dường như với tôi rằng nếu chúng ta mong đợi khả năng một phương pháp sẽ không thành công, thì thất bại của nó không phải là một ngoại lệ. Vì vậy, phương thức tôi đang gọi sẽ trả về true nếu thành công và sai nếu thất bại.

Tại sao nó là một Func<bool, bool>và không chỉ là một Func<bool>? Vì vậy mà nếu tôi muốn một phương pháp có thể đưa ra một ngoại lệ khi thất bại, tôi có một cách để thông báo rằng đây là lần thử cuối cùng.

Vì vậy, tôi có thể sử dụng nó với mã như:

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

hoặc là

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

Nếu việc truyền tham số mà phương thức không sử dụng chứng tỏ là khó xử, thì việc thực hiện quá tải Retrycũng chỉ Func<bool>là chuyện nhỏ.


1
+1 để tránh ngoại lệ. Mặc dù tôi sẽ làm một khoảng trống Thử lại (...) và ném một cái gì đó? Trả về Boolean và / hoặc mã trả lại quá thường bị bỏ qua.
csharptest.net

1
"Nếu chúng tôi mong đợi khả năng một phương pháp sẽ không thành công, thì thất bại của nó không phải là ngoại lệ" - trong khi đó, điều đó đúng trong một số trường hợp, ngoại lệ không có nghĩa là ngoại lệ. Đó là để xử lý lỗi. Không có gì đảm bảo rằng người gọi sẽ kiểm tra kết quả Boolean. Có một sự đảm bảo rằng một ngoại lệ sẽ bị xử lý (bằng thời gian chạy tắt ứng dụng nếu không có gì khác không).
TrueWill

Tôi không thể tìm thấy tài liệu tham khảo nhưng tôi tin .NET định nghĩa một Ngoại lệ là "một phương thức không làm những gì nó nói nó sẽ làm". 1 mục đích là sử dụng các ngoại lệ để chỉ ra một vấn đề thay vì mẫu Win32 yêu cầu người gọi kiểm tra giá trị trả về nếu chức năng thành công hay không.
noctonura

Nhưng các trường hợp ngoại lệ không chỉ đơn thuần là "chỉ ra một vấn đề." Chúng cũng bao gồm một khối lượng thông tin chẩn đoán tốn thời gian và bộ nhớ để biên dịch. Rõ ràng có những tình huống không quan trọng chút nào. Nhưng có rất nhiều nơi nó làm. .NET không sử dụng ngoại lệ cho luồng điều khiển (so sánh, với việc sử dụng StopIterationngoại lệ của Python ) và có một lý do.
Robert Rossney

Các TryDomô hình phương pháp là một dốc trơn trượt. Trước khi bạn biết điều đó, toàn bộ ngăn xếp cuộc gọi của bạn sẽ bao gồm các TryDophương thức. Các ngoại lệ được phát minh để tránh một mớ hỗn độn như vậy.
HappyNomad

2

Backoffential mũ là một chiến lược thử lại tốt hơn là chỉ đơn giản là thử x số lần. Bạn có thể sử dụng một thư viện như Polly để thực hiện nó.


2

Đối với những người muốn có cả hai tùy chọn để thử lại bất kỳ ngoại lệ nào hoặc đặt rõ ràng loại ngoại lệ, hãy sử dụng:

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

Tôi cần một phương pháp hỗ trợ hủy bỏ, trong khi tôi đang ở đó, tôi đã thêm hỗ trợ để trả về các lỗi trung gian.

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

Bạn có thể sử dụng Retrychức năng như thế này, thử lại 3 lần với độ trễ 10 giây nhưng không hủy.

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

Hoặc, thử lại vĩnh viễn cứ sau năm giây, trừ khi bị hủy.

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

Như bạn có thể đoán, trong mã nguồn của tôi, tôi đã quá tải Retrychức năng để hỗ trợ các loại ký hiệu khác nhau mà tôi muốn sử dụng.


2

Phương pháp này cho phép thử lại trên các loại ngoại lệ nhất định (ném người khác ngay lập tức).

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

Ví dụ sử dụng:

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

1

Cập nhật sau 6 năm: bây giờ tôi cho rằng cách tiếp cận dưới đây khá tệ. Để tạo logic thử lại, chúng ta nên xem xét sử dụng một thư viện như Polly.


Tôi asyncthực hiện phương pháp thử lại:

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

Điểm chính: Tôi đã sử dụng .ConfigureAwait(false);Func<dynamic>thay vào đóFunc<T>


Điều này không cung cấp một câu trả lời cho câu hỏi. Vui lòng xem xét việc đăng câu trả lời của bạn dưới dạng câu hỏi mới, sử dụng nút "Đặt câu hỏi" ở đầu trang, sau đó đăng câu trả lời của riêng bạn cho câu hỏi để chia sẻ những gì bạn đã học được với cộng đồng.
elixenide

Đơn giản hơn nhiều với C # 5.0 so với codereview.stackexchange.com/q/55983/54000 nhưng có lẽ nên tiêm CansellactionToken.
SerG

Có một vấn đề với việc thực hiện này. Sau lần thử lại cuối cùng, ngay trước khi từ bỏ, Task.Delayđược gọi mà không có lý do.
HappyNomad

@HappyNomad đây là một câu trả lời 6 năm tuổi và bây giờ tôi cho rằng đó là một cách tiếp cận khá tệ để tạo ra logic thử lại :)) cảm ơn vì đã thông báo. Tôi sẽ cập nhật câu trả lời của tôi theo sự cân nhắc đó.
Cihan Uygun

0

Hoặc làm thế nào về việc làm nó gọn gàng hơn một chút ....

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

Tôi tin rằng việc ném ngoại lệ thường nên tránh như một cơ chế trừ khi bạn vượt qua chúng giữa các ranh giới (chẳng hạn như xây dựng thư viện mà người khác có thể sử dụng). Tại sao không có DoSomething()lệnh trả về truenếu nó thành công vàfalse nếu không?

EDIT: Và điều này có thể được gói gọn trong một chức năng như những người khác đã đề xuất. Vấn đề duy nhất là nếu bạn không tự viết DoSomething()hàm


7
"Tôi tin rằng việc ném ngoại lệ nói chung nên tránh như một cơ chế trừ khi bạn vượt qua chúng giữa các ranh giới" - Tôi hoàn toàn không đồng ý. Làm thế nào để bạn biết người gọi đã kiểm tra trả lại sai (hoặc tệ hơn, null) của bạn? TẠI SAO mã không thành công? Sai cho bạn không có gì khác. Điều gì sẽ xảy ra nếu người gọi phải vượt qua thất bại trong ngăn xếp? Đọc msdn.microsoft.com/en-us/l Library / ms229014.aspx - đây là những thư viện, nhưng chúng có ý nghĩa tương tự đối với mã nội bộ. Và trong một nhóm, những người khác có thể gọi mã của bạn.
TrueWill

0

Tôi đã có nhu cầu chuyển một số tham số cho phương thức của mình để thử lại và có giá trị kết quả; vì vậy tôi cần một biểu thức .. Tôi xây dựng lớp này thực hiện công việc (nó được truyền cảm hứng cho lớp LBushkin) bạn có thể sử dụng nó như thế này:

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. giải pháp của LBushkin thực hiện thêm một lần thử lại = D


0

Tôi sẽ thêm đoạn mã sau vào câu trả lời được chấp nhận

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

Về cơ bản mã trên đang làm cho Retrylớp chung chung để bạn có thể vượt qua loại ngoại lệ bạn muốn bắt để thử lại.

Bây giờ sử dụng nó gần như theo cùng một cách nhưng chỉ định loại ngoại lệ

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

Vòng lặp for sẽ luôn thực thi một vài lần (dựa trên retryCount của bạn) ngay cả khi mã trong vòng lặp TRY CATCH được thực thi mà không có ngoại lệ. Tôi sẽ đề nghị đặt retryCount bằng với var retry trong vòng lặp thử, vì vậy vòng lặp for sẽ dừng đi qua nó.
scre_www

@scre_www Tôi tin rằng bạn đã nhầm. Nếu actionkhông ném thì Dotrả về do đó breakđi ra khỏi forvòng lặp.
HappyNomad

Trong mọi trường hợp, có một vấn đề với việc thực hiện này. Sau lần thử lại cuối cùng, ngay trước khi từ bỏ, Thread.Sleepđược gọi mà không có lý do.
HappyNomad

0

Tôi biết câu trả lời này rất cũ nhưng tôi chỉ muốn bình luận về vấn đề này bởi vì tôi đã gặp phải các vấn đề khi sử dụng những điều này trong khi làm, bất cứ điều gì với các quầy.

Trong những năm qua tôi đã giải quyết một cách tiếp cận tốt hơn tôi nghĩ. Đó là sử dụng một số loại tổng hợp sự kiện như phần mở rộng phản ứng "Chủ đề" hoặc tương tự. Khi thử thất bại, bạn chỉ cần xuất bản một sự kiện cho biết thử không thành công và có chức năng tổng hợp lên lịch lại sự kiện. Điều này cho phép bạn kiểm soát nhiều hơn đối với thử lại mà không gây ô nhiễm chính cuộc gọi với một loạt các vòng lặp thử lại và những gì không. Bạn cũng không buộc một sợi chỉ với một loạt các sợi ngủ.


0

Làm điều đó đơn giản bằng C #, Java hoặc các ngôn ngữ khác:

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

và sử dụng nó trong mã của bạn rất đơn giản:

 void simpleMethod(){
    //some code

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

    //some code    
    }

hoặc bạn có thể sử dụng nó trong các phương pháp đệ quy:

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

Bạn sẽ làm gì với Request.BadRequest?
Danh tiếng

0

Retry helper: một triển khai java chung có chứa cả hai lần thử lại có thể trả về và void.

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

Sử dụng:

    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

Đây là một async/ awaitphiên bản tổng hợp các ngoại lệ và hỗ trợ hủy bỏ.

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

Bởi vì throw;cách duy nhất là vòng lặp vô hạn bị chấm dứt, phương pháp này thực sự đang thực hiện "thử cho đến khi nó thất bại N lần" và không phải là "thử đến Nlần cho đến khi thành công". Bạn cần một break;hoặc return;sau cuộc gọi đến ThingToTryDelegate();nếu không nó sẽ được gọi liên tục nếu không bao giờ thất bại. Ngoài ra, điều này sẽ không được biên dịch vì tham số đầu tiên TryNTimeskhông có tên. -1.
BACON

-1

Tôi đã viết một lớp học nhỏ dựa trên câu trả lời được đăng ở đây. Hy vọng nó sẽ giúp được ai đó: 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

Tôi đã triển khai một phiên bản không đồng bộ của câu trả lời được chấp nhận như vậy - và nó dường như hoạt động tốt - có nhận xét nào không?


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

Và, gọi nó đơn giản như thế này:

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

Thread.Sleep? Chặn một chuỗi phủ nhận lợi ích của sự không đồng bộ. Ngoài ra tôi khá chắc chắn rằng Task DoAsync()phiên bản nên chấp nhận một đối số của loại Func<Task>.
Theodor Zoulias
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.