Carl Franklin has mentioned Polly on four .NET Rocks! episodes now during his Better Know a Framework segment. He first mentioned it on show #1134, which happened to be when I was dealing with an issue for which Polly is a perfect fit.
You may be wondering what Polly is, or why in the heck you might want to use it.
Not Your Pirate's Polly
The concise description ripped straight from the Polly GitHub page sums it up nicely:
It provides a great way to handle application exceptions in your code and clearly describe what to do when they occur (and they will), and whether you should keep trying the process that failed and for how long.
You don't feed this Polly crackers. He loves nasty exceptions instead.
A Practical Use Case
Why use this library instead of mainstream exception handling?
I wondered the same thing when I first came across Polly. So, I'll leave you with a use case where I implemented Polly in a way that would've cost me many more keystrokes.
I developed a Xamarin-based mobile app used by field engineers to take pictures after work is done on a job site and to fill out a quality control check sheet. The app is built to run offline or within occasionally connected scenarios. When they are ready to submit their data to the intake service, they might not have the best of connections.
Polly provides an interesting retry policy when an exception is encountered. Now, consider this: you've got 30 reasonably large photos to upload to the cloud. You may not have the most reliable data connection, but it's mostly there. You need to upload these photos while you can. So, the way I handled it is to set an exponential retry policy that waits for longer durations each time an exception is encountered before trying again. You can clearly see the exception handling here and also easily understand the retry logic.
Here's a code sample from my project. If I were to write this from scratch, it would take a lot more time to do, so why reinvent the wheel?
// Retry a specified number of times, using a function to // calculate the duration to wait between retries based on // the current retry attempt (allows for exponential backoff), // calling an action on each retry with the current exception, // duration and context provided to Execute() // In this case will wait for // 1 ^ 2 = 2 seconds then // 2 ^ 2 = 4 seconds then // 3 ^ 2 = 8 seconds then // 4 ^ 2 = 16 seconds then // 5 ^ 2 = 32 seconds await Policy .Handle<Exception>() .WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds (Math.Pow (2, retryAttempt)), (exception, timeSpan, context) => { // Send exception logging to Xamarin.Insights: MessagingCenter.Send<MobileServiceClient, ExceptionContainerException> (QCApp.Client, Common.Constants.MessageCenterMessages.MessagingCenterError, new ExceptionContainerException { Title = "Error uploading image", Exception = exception }); Insights.Report (exception, new Dictionary<string, string> { { "Filename", "SectionViewModel.cs" }, { "Where", "UploadImage" }, { "Issue", "Error uploading image" } }); } ) .ExecuteAsync(async () => // Moved away from using the Microsoft.WindowsAzure.Storage SDK in // favor of direct REST calls due to the SDK's slowness. REST calls appear to run about // 15x faster for uploads! BlobTransfer.UploadFileAsync(picture, picture.Filename) );
Pretty clear and concise, isn't it?
By the way, implementing this exponential retry cut down failed upload attempts to almost zero.
Let's walk through the code in order to better understand what Polly is doing:
await Policy .Handle<Exception>()
The method I'll be calling is asynchronous, which Polly supports, hence the await keyword. Here we specify the type of exception to handle. In my case, handling a base Exception type is sufficient. You could handle more specific Exception types, such as SqlException.
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds (Math.Pow (2, retryAttempt)), (exception, timeSpan, context) => { // Send exception logging to Xamarin.Insights: MessagingCenter.Send<MobileServiceClient, ExceptionContainerException> (QCApp.Client, Common.Constants.MessageCenterMessages.MessagingCenterError, new ExceptionContainerException { Title = "Error uploading image", Exception = exception }); Insights.Report (exception, new Dictionary<string, string> { { "Filename", "SectionViewModel.cs" }, { "Where", "UploadImage" }, { "Issue", "Error uploading image" } }); } )
There are several policies that determine how the policy should handle the exception(s): Retry, Retry forever, Retry and Wait, and Circuit Breaker. In this case, I'm using the Retry and Wait method (WaitAndRetryAsync, WaitAndRetry for non-asynchronous methods). Here I'm using a lambda expression to calculate the duration exponentially between retries, based off of the current retry attempt (retryAttempt). Of the passed in parameters, the exception is the one I'm most interested in. With it, I am displaying an error message to the user, as well as sending the details of the exception to Xamarin Insights so I can triage the issue later on.
.ExecuteAsync(async () => // Moved away from using the Microsoft.WindowsAzure.Storage SDK in // favor of direct REST calls due to the SDK's slowness. REST calls appear to run about // 15x faster for uploads! BlobTransfer.UploadFileAsync(picture, picture.Filename) );
This is where I am executing my asynchronous method by way of the policy's ExecuteAsync action. If this method fails for some reason and throws an exception as a result, the policy's exception handling takes over and, in my case, attempts to execute the method over a logarithmic time curve.
If you were paying attention, you'd notice my comment within the ExecuteAsync method about ditching the Azure Storage SDK in favor of native REST calls due to the SDK's sluggishness. I'm not exaggerating that I reduced the upload time of my images by about 15 times! I'll write up a blog post about that in the future, if you're interested.