Reputation: 938
I'm trying to retry a failed operation 3 times.
I'm using Polly for a retry operation.
I want to get the exception in case the retry operation fails and retry again 2 times and so on.
return await Policy
.Handle<CustomException>()
.RetryAsync(3, onRetryAsync: async (exception, retryCount, context) =>
{
return await runner.run(params);
});
The function should return
Task<IReadOnlyCollection<string>>
I'm getting the following error:
async lambda expression converted to a task returning delegate cannot return a value
Upvotes: 22
Views: 41975
Reputation: 22819
Even though Crowcoder's post provides a working code, it does not really explain too much IMHO.
Polly was designed in a way that you define a policy first then you execute it. Most of the times these two steps are separated in time and space but there are occasions when you just want to do it right away.
var result = Policy
.Handle<Exception>()
.RetryAsync(...)
.ExecuteAsync(...);
I understand that many people got confused with these method chaining (two XYZAsync
methods). This is mainly because of the absence of the Build
method call. In the V7 (and prior) API you don't have an explicit I'm done with the policy definition, now I'm ready to execute it.
In the V8 API you have to explicitly call the Build
on the ResiliencePipelineBuilder
to get a ResiliencePipeline
which can be executed.
onRetry{Async}
Polly was designed in a way to allow the library consumers to provide callbacks to react on certain actions. In the old .NET world these would be events but with the newer directions these are callbacks.
In other words Polly policies implement the template method design pattern. Each policy defines a set of statements that needs to be executed in a given order and provides customization points (in the design pattern they are called steps). In the original pattern the steps could be defined/overridden via inheritance. In case of Polly this is done via callback.
The onRetry{Async}
callback is called
If you define your retry policy with Retry{Async}
then there is no delay between the retry attempts. If you use WaitAndRetry{Async}
then you can instruct Polly to take a rest before kicking off a new attempt.
My main point here is that onRetry{Async}
is a notification hook. So, normally you don't want to execute any business logic here rather just propagate the notification to a persistence storage for tracing.
onRetryAsync
overloadsOne of the biggest criticism against Polly V7: there were way too many overloads. Just look at this file: AsyncRetrySyntax
. It contains almost 20 overloads
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<int, TimeSpan> sleepDurationProvider)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<int, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, int, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, int, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, Context, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, Context, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<int, Context, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, int, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, Context, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, int, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount,
Func<int, Exception, Context, TimeSpan> sleepDurationProvider, Func<Exception, TimeSpan, int, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Action<Exception, TimeSpan> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Func<Exception, TimeSpan, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Action<Exception, TimeSpan, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Func<Exception, TimeSpan, Context, Task> onRetryAsync)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Action<Exception, TimeSpan, int, Context> onRetry)
public static AsyncRetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IEnumerable<TimeSpan> sleepDurations, Func<Exception, TimeSpan, int, Context, Task> onRetryAsync)
It is pretty easy to got lost. Even for those who were using this API quite frequently.
Another big problem with this: it is also not extensibility friendly. Let me show you an example:
var retryPolicy = Policy
.Handle<Exception>()
.RetryAsync(3, onRetry: (exception, retryCount, context) =>
{
Console.WriteLine(exception.Message);
});
Let's extend this with a new clause to trigger for negative numbers as well
var retryPolicy = Policy
.Handle<Exception>()
.OrResult<int>(Int32.IsNegative)
.RetryAsync(3, onRetry: (exception, retryCount, context) =>
{
Console.WriteLine(exception.Message);
});
This code won't compile anymore with the following error (pointing to the exception.Message
):
DelegateResult<int>
does not contain a definition forMessage
and no accessible extension methodMessage
accepting a first argument of typeDelegateResult<int>
could be found (are you missing a using directive or an assembly reference?
So, what happened? The first version of the policy triggers only if an exception occurred. So, the first parameter of the onRetry
was indeed an Exception
.
The second version of the policy triggers either if an exception occurred or a negative number is returned. So, the first parameter of the onRetry
now is DelegateResult<int>
and not an Exception
anymore.
The DelegateResult
does not have a Message
property rather a Result
and an Exception
. That's why we have the compilation error.
All in all it takes some time to get familiar with the V7 API and its design decisions. V8 is a complete rewrite which tried to address all of these problems.
Upvotes: 1
Reputation: 11514
I think it is unusual to run your logic in the retry policy - unless I misunderstand your question. More typically you execute the policy by calling a method that runs your logic.
Something like this:
async Task Main()
{
var polly = Policy
.Handle<Exception>()
.RetryAsync(3, (exception, retryCount, context) => Console.WriteLine($"try: {retryCount}, Exception: {exception.Message}"));
var result = await polly.ExecuteAsync(async () => await DoSomething());
Console.WriteLine(result);
}
int count = 0;
public async Task<string> DoSomething()
{
if (count < 3)
{
count++;
throw new Exception("boom");
}
return await Task.FromResult("foo");
}
output
try: 1, Exception: boom
try: 2, Exception: boom
try: 3, Exception: boom
foo
Upvotes: 40