Retrying failed methods is crucial for ensuring reliability and efficiency in systems such as database transactions or online services. As temporary faults such as network congestion, hardware issues, or software glitches can cause initial failures, it is important to implement retry mechanisms with exponential backoff to increase the probability of success, minimize data loss, and provide a seamless user experience in the face of transient obstacles. Retry mechanisms enable systems to be dependable and resilient.
This series of articles describes how to construct an aspect that automatically retries a failed method. This aspect modifies a method in the following way:
1internal class RemoteCalculator
2{
3 private static int _attempts;
4
5 [Retry( Attempts = 5 )]
6 public int Add( int a, int b )
7 {
8 // Let's pretend this method executes remotely
9 // and can fail for network reasons.
10
11 Thread.Sleep( 10 );
12
13 _attempts++;
14 Console.WriteLine( $"Trying for the {_attempts}-th time." );
15
16 if ( _attempts <= 3 )
17 {
18 throw new InvalidOperationException();
19 }
20
21 Console.WriteLine( $"Succeeded." );
22
23 return a + b;
24 }
25
26 [Retry( Attempts = 5 )]
27 public async Task<int> AddAsync( int a, int b )
28 {
29 // Let's pretend this method executes remotely
30 // and can fail for network reasons.
31
32 await Task.Delay( 10 );
33
34 _attempts++;
35 Console.WriteLine( $"Trying for the {_attempts}-th time." );
36
37 if ( _attempts <= 3 )
38 {
39 throw new InvalidOperationException();
40 }
41
42 Console.WriteLine( $"Succeeded." );
43
44 return a + b;
45 }
46}
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4using Microsoft.Extensions.Logging;
5
6internal class RemoteCalculator
7{
8 private static int _attempts;
9
10 [Retry( Attempts = 5 )]
11 public int Add( int a, int b )
12 {
13for (var i = 0; ; i++)
14 {
15 try
16 {
17 // Let's pretend this method executes remotely
18 // and can fail for network reasons.
19
20 Thread.Sleep( 10 );
21
22 _attempts++;
23 Console.WriteLine( $"Trying for the {_attempts}-th time." );
24
25 if ( _attempts <= 3 )
26 {
27 throw new InvalidOperationException();
28 }
29
30 Console.WriteLine( $"Succeeded." );
31
32 return a + b;
33}
34 catch (Exception e) when (i < 5)
35 {
36 var delay = 100 * Math.Pow(2, i + 1);
37 _logger.LogWarning($"RemoteCalculator.Add(a = {{{a}}}, b = {{{b}}}) has failed: {e.Message} Retrying in {delay} ms.");
38 Thread.Sleep((int)delay);
39 _logger.LogTrace($"RemoteCalculator.Add(a = {{{a}}}, b = {{{b}}}): retrying now.");
40 }
41 }
42 }
43
44 [Retry( Attempts = 5 )]
45 public async Task<int> AddAsync( int a, int b )
46 {
47for (var i = 0; ; i++)
48 {
49 try
50 {
51 return (int)(await this.AddAsync_Source(a, b));
52 }
53 catch (Exception e) when (i < 5)
54 {
55 var delay = 100 * Math.Pow(2, i + 1);
56 _logger.LogWarning($"RemoteCalculator.AddAsync(a = {{{a}}}, b = {{{b}}}) has failed: {e.Message} Retrying in {delay} ms.");
57 await Task.Delay((int)delay);
58 _logger.LogTrace($"RemoteCalculator.AddAsync(a = {{{a}}}, b = {{{b}}}): retrying now.");
59 }
60 }
61 }
62private async Task<int> AddAsync_Source(int a, int b)
63 {
64 // Let's pretend this method executes remotely
65 // and can fail for network reasons.
66
67 await Task.Delay( 10 );
68
69 _attempts++;
70 Console.WriteLine( $"Trying for the {_attempts}-th time." );
71
72 if ( _attempts <= 3 )
73 {
74 throw new InvalidOperationException();
75 }
76
77 Console.WriteLine( $"Succeeded." );
78
79 return a + b;
80 }
81 private ILogger _logger;
82
83 public RemoteCalculator(ILogger<RemoteCalculator> logger = null)
84 {
85 this._logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
86 }
87}
In this series
We start with the most basic implementation and add features progressively.
Description | Article |
---|---|
Retry example, step 1: Getting started | This is the most basic retry aspect. |
Retry example, step 2: Handling async methods | In this example, we add support for async methods and call await Task.Delay instead of Thread.Sleep . |
Retry example, step 3: Handling cancellation tokens | Here, we add support for CancellationToken parameters, which we pass to Task.Delay . |
Retry example, step 4: Adding logging | We now add proper logging using ILogger and dependency injection. |
Retry example, step 5: Using Polly | Finally, we show how to use Polly instead of our custom and naïve implementation of the retry logic. |