-
Notifications
You must be signed in to change notification settings - Fork 0
Using Proteus.Retry
See the ReadMe in the Github Repo for a quick getting-started guide.
The first step in using Proteus.Retry is to create a new instance of the Retry
class which you will subsequently use to invoke your own method. There are two available constructors for the Retry
class, one zero-arg ctor and another that takes a RetryPolicy
instance.
var retry = new Retry(); //zero-arg default ctor
// ...or...
var policy = new RetryPolicy();
var retry = new Retry(policy); //ctor that takes a RetryPolicy instance
If you choose the default zero-arg ctor approach, you will be constructing a Retry
instance with a default RetryPolicy
. For more detail on the default RetryPolicy
, see the Retry Policies section of this Wiki, but the important thing to note is that the default RetryPolicy
declares zero (no) retry attempts. This means that invoking your own code with the default RetryPolicy
is functionally equivalent to invoking your code directly without Retry
adding any value.
If you construct a Retry
instance using the default ctor, you should (usually) ensure that you apply a non-default RetryPolicy
to the Retry
instance before using it to invoke your own code. This can be done using the .Policy
property of the Retry
class as follows:
var retry = new Retry();
var policy = new RetryPolicy();
//TODO: configure properties of the 'policy' instance here
retry.Policy = policy;
For the sake of this (real-world) example, let's assume that you have the following class defined. MyServiceWrapper
contains a single method, .DoWork(...)
that makes a remote call over the network to a service to return some value. For the sake of our example, let's assume it uses .NET Remoting (so that we can assume that the type of Exceptions that might need to be retried are related to .NET Remoting), although the same principle applies to any other method that might experience transient failures.
public class MyServiceWrapper
{
public int DoWork(int input)
{
var service = new RemoteService(); //something capable of calling the service
return service.GetResult(input);
}
}
Once a Retry
instance has been created, using it to perform work is as simple as the following:
//create and configure the RetryPolicy
var policy = new RetryPolicy();
policy.MaxRetries = 4;
policy.MaxRetryDuration = TimeSpan.FromSeconds(10);
policy.RegisterRetriableException<RemotingException>();
//create the Retry and assign the RetryPolicy
var retry = new Retry(policy);
//create an instance of your own class
var myService = new MyServiceWrapper;
//use Retry to invoke the method
var result = retry.Invoke(() => myService.DoWork(200));
//'result' now holds the return value from the myService.DoWork(...) invocation
Its important to understand that Retry
does not inherently attempt to perform retries on e.g., background or other threads (although its entirely supported to invoke the entirety of Retry
on other threads; see Working wth Threading and Async Methods for more detail).
When using Retry
to invoke your method, no matter how many times your method invocation is retried, one of the following will be the (eventual) result:
Successful Outcomes
- The return value of your method is returned (if your method is a
Func<T>
) - void is returned (if your method is an
Action
-- i.e., a void-returning method)
Unsuccessful Outcomes
- A
MaxRetryCountExceededException
will be thrown byRetry
if unsuccessful before reaching the.MaxRetries
value of the governingRetryPolicy
- A
MaxDurationExpiredException
will be thrown byRetry
if unsuccessful before the.MaxRetryDuration
of the governingRetryPolicy
expires - If at any point
Retry
received an 'unexpected' exception for which the governingRetryPolicy
has not been configured to consider 'retriable', retries will immediately cease and this 'unexpected' exception will be rethrown to your calling code.
As you can see from this list of possible outcomes, this design has several impacts on your code:
- The flow of your program code that calls
Retry
is blocked during aRetry
invocation and will not advance to the next instruction until one of the several above-listed outcomes (either successful or unsuccessful) is reached. - Because unsuccessful cases all involve throwing exceptions, your calling code should consider a
try...catch
strategy to react to any unsuccessful outcomes as shown in the following section.
Following is the common usage pattern of the recommended try...catch
structure for interacting with Retry
:
//TODO: construct Retry and configure RetryPolicy as needed
try
{
retry.Invoke(() => yourObject.YourMethod(yourArg1, yourArg2));
}
catch (MaxRetryCountExceededException ex)
{
//TODO: respond to the case where number of retries wasn't sufficient
}
catch (MaxDurationExpiredException ex)
{
//TODO: respond to the case where insufficient time was available
}
catch (Exception ex) //or specific type as desired
{
//TODO: respond to any unexpected exceptions
}
Since the specific failure exceptions thrown by Retry
all inherit from a common base class (RetryInvocationFailureException
) and catch
blocks in .NET are polymorphic, if you are not interested in why an invocation was unsuccessful (e.g., whether it was too many retries or that the time allotted for all retries had expired), its easy to collapse the above structure into a more coarse-grained try...catch
block as follows:
//TODO: construct Retry and configure RetryPolicy as needed
try
{
retry.Invoke(() => yourObject.YourMethod(yourArg1, yourArg2));
}
catch (RetryInvocationFailureException ex)
{
//TODO: respond to the case where the retry was unsuccessful for *whatever* reason
}
catch (Exception ex) //or specific type as desired
{
//TODO: respond to any unexpected exceptions
}