0

I'm trying to do the following using Polly. I have an ExecuteTask async method, and I want to perform a conditional retry with a 200 ms wait. This is what I have so far:

The policy

AsyncRetryPolicy<SomeData> retryPolicy = AsyncPolicy
    .HandleResult<SomeData>(s => s.IsCorrect == false)
    .WaitAndRetry(1,
    sleepDurationProvider: (retryCount, status, ctx) =>
    {
        return TimeSpan.FromMilliseconds(200);
    },
    onRetry: (response, timeSpan, retryCount, ctx) =>
    {
        Console.WriteLine($"Received a response of {response.Result}, retrying {retryCount}.");
    });

The to-be-decorated method

private static async Task<SomeData> ExecuteTask()
{
    return new SomeData() { IsCorrect= false };
}

The execution

var rslt = retryPolicy.ExecuteAsync(async () => {
        return await ExecuteTask().ConfigureAwait(false);
    });

SomeData is just a class with a bool property, called IsCorrect.

I can't seem the figure out how to define this policy. AsyncPolicy does not contain a HandleResult() method.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
gilpach
  • 1,367
  • 3
  • 16
  • 34

3 Answers3

1

You start by creating a PolicyBuilder<SomeData> via Policy.HandleResult<>(), then you produce an async policy by calling WaitAndRetryAsync. You just need to be careful to get all the delegate signatures correct or it won't recognize the method. It can help with debugging if you pass an explicit type into the WaitAndRetryAsync<>() method.

AsyncRetryPolicy<SomeData> retryPolicy = Policy.HandleResult<SomeData>(s => s.IsCorrect == false)
    .WaitAndRetryAsync<SomeData>(1,
    sleepDurationProvider: _ =>
    {
        return TimeSpan.FromMilliseconds(200);
    },
    onRetry: (response, timeSpan, retryCount, ctx) =>
    {
        Console.WriteLine($"Received a response of {response}, retrying {retryCount}.");
    });

var rslt = await retryPolicy.ExecuteAsync(async () =>
{
    return await ExecuteTask().ConfigureAwait(false);
});
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
1

Use Policy.HandleResult and invoke WaitAndRetryAsync on it:

AsyncRetryPolicy<SomeData> retryPolicy = Policy.HandleResult<SomeData>(s => s.IsCorrect == false)
     .WaitAndRetryAsync(1,
         sleepDurationProvider: _ => TimeSpan.FromMilliseconds(200),
         onRetry: (response, timeSpan, retryCount, ctx) =>
         {
             Console.WriteLine($"Received a response of {response}, retrying {retryCount}.");
         });

var rslt = await retryPolicy.ExecuteAsync(async () => await ExecuteTask().ConfigureAwait(false));
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

In case of Polly we can use the Builder Pattern to define policies:

  1. You can start to define a policy either via the Policy or via the Policy<TResult> classes
  2. They expose static methods so, whenever you are calling the Handle<TException> or HandleResult<TResult> on it you will receive a PolicyBuilder or a PolicyBuilder<TResult> instance
  3. They expose instance methods so, if you want to define additional triggers then you can do that via Or<TException>, OrInner<TException>, or OrResult<TResult>
    3.1. Each instance method returns the builder
  4. There are extension methods to define the required behaviour, like WaitAndRetry or WaitAndRetryForeverAsync<TResult>
    4.1 These return with RetryPolicy and AsyncRetryPolicy<TResult> classes respectively

In order words:

  • Get a builder instance: decide that your to-be-decorated thing is a method or a function
  • Configure the builder: define the trigger(s)
  • Build the policy: define the actual behaviour

There are so many classes and interfaces in Polly and one can easily lost there. Let me try to guide you through these with an example: synchronous retry policy for a method:

Classes

  • RetryEngine: This is the actual implementation and it has many responsibilities: check whether the policy should be triggered, calculate the sleep duration, call user provided delegate, execute the decorated method, etc.
  • RetryPolicy: It is a thin wrapper around the engine. In other words it is the publicly exposed API to reach the engine
    • Because the retry policy is stateless that's why this wrapper is thin
    • But for instance the Circuit Breaker policy is stateful, so its policy class stores state and a reference to a controller << not so thin
  • PolicyBase: This contains the common elements for all kinds of policies regardless they are defined as sync or async and regardless defined for method or function
  • Policy: This class defines many common functionality for building a policy, executing it (Execute or ExecuteAndCaptureAsync) and handling context, etc.

Interfaces


Because of the above ones the following policy definitions are (more or less) identical:

AsyncRetryPolicy<SomeData> retryPolicy = Policy.HandleResult<SomeData>(...
IRetryPolicy<SomeData> retryPolicy = Policy.HandleResult<SomeData>(...
AsyncPolicy<SomeData> retryPolicy = Policy<SomeData>.HandleResult(...
IAsyncPolicy<SomeData> retryPolicy = Policy<SomeData>.HandleResult(...
Peter Csala
  • 17,736
  • 16
  • 35
  • 75