C# 錯誤處理(重試)類別庫 Polly 示範

身為軟體工程師,有許多事情是我們無法控制的,例如我們呼叫的 API 有時候就是失敗,有時候檔案就是被占用無法讀取。為了避免遇到這些萬一,都需要寫一堆 while 或 for 迴圈再加上 try catch ,專門處理這些現實生活中可能會遇到的無數意外。其實有個好用的第三方類別庫可以幫我們處理這件事情,就是今天要介紹的 Polly ,該類別庫有加入 .NET 基金會(.NET Foundation),下載次數達 284.5M ,是非常值得一試的。

安裝套件

先使用 NuGet 安裝 Polly 套件,或是使用 .NET CLI 執行以下指令安裝套件
	
dotnet add package Polly
    

實際問題範例

先看個例子就可以了解 Polly 能夠做些什麼了

假設我們現在要寫個小程式,偵測到檔案建立,就把檔案移動到特定的資料夾下。如果有經驗就知道只要檔案稍微大一點點,就很容易出現下面這個錯誤:
    
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\Users\ruyut\Desktop\test.desc'.
File name: 'C:\Users\ruyut\Desktop\test.desc'
   at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.ReadAllText(String path, Encoding encoding)
   at Program.<>c.<<Main>$>b__0_1() in C:\Users\ruyut\Documents\RiderProjects\test\2022\ConsoleAppPollyTest\ConsoleAppPollyTest\Program.cs:line 30
   at Polly.Policy.<>c__DisplayClass108_0.<Execute>b__0(Context _, CancellationToken _)
   at Polly.Policy.<>c__DisplayClass138_0.<Implementation>b__0(Context ctx, CancellationToken token)
   at Polly.Retry.RetryEngine.Implementation[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Action`4 on
Retry, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider)
   at Polly.Retry.RetryPolicy.Implementation[TResult](Func`3 action, Context context, CancellationToken cancellationToken)
   at Polly.Policy.Implementation(Action`2 action, Context context, CancellationToken cancellationToken)
   at Polly.Policy.Execute(Action`2 action, Context context, CancellationToken cancellationToken)
   at Polly.Policy.Execute(Action action)
   at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\test\2022\ConsoleAppPollyTest\ConsoleAppPollyTest\Program.cs:line 28
    

這可能是因為檔案寫入到一半,或是檔案從其他地方複製過來,還沒關閉,我們程式想要讀取時就會出現這個錯誤。假設出現這個錯誤要重試 3 次,使用 Polly 可以很簡單:
    
var policy = Policy
    .Handle<IOException>() // 主要處理的例外
    .Or<FileNotFoundException>() // 次要例外 (可選)
    .WaitAndRetry(new[] // 等待並重試
    {
        TimeSpan.FromSeconds(1), // 第 1 次等 1 秒
        TimeSpan.FromSeconds(2), // 第 2 次等 2 秒
        TimeSpan.FromSeconds(3), // 第 3 次等 3 秒
    }, (exception, timeSpan, retryCount, context) => // 重試前執行動作
    {
        Console.WriteLine($"第 {retryCount} 次重試");
    });

policy.Execute(() =>
{
    string readAllText = File.ReadAllText(@"C:\Users\ruyut\Desktop\test.desc");
    Console.WriteLine($"讀取成功: {readAllText}");
});


// 如果第三次嘗試還是錯誤(包含第一次執行,加三次重試,共四次),就會拋出錯誤
    

除了等待並重試外還有許多常用的方式

重試(不等待)

    
var policy = Policy
    .Handle<IOException>() // 主要處理的例外
    .Or<FileNotFoundException>() // 次要例外 (可選)
    .Retry(3, (exception, retryCount) => { // 重試 3 次(不等待)
        // 重試前執行動作
        Console.WriteLine($"Retry {retryCount} times");
    });
    

永遠重試(不等待)

此方式不成功不停止,比較少會使用到
    
var policy = Policy
    .Handle<IOException>() // 主要處理的例外
    .Or<FileNotFoundException>() // 次要例外 (可選)
    .RetryForever(onRetry: exception => // 永遠重試(不等待)
    {
    	// 重試前執行動作
        Console.WriteLine("再次重試");
    });
    

等待並永遠重試

此方式不成功不停止,比較少會使用到
    
var policy = Policy
    .Handle<IOException>() // 主要處理的例外
    .Or<FileNotFoundException>() // 次要例外 (可選)
    .WaitAndRetryForever( // 等待並永遠重試
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        (exception, timeSpan, context) => // 重試前執行動作
        {
            Console.WriteLine("再次重試");
        }
    );
    



參考資料:
Github - Polly

留言