Mobile networking introduces connectivity challenges that don’t generally exist in more traditional server-based or desktop-based development. Sure iOS, Android, and Windows Phone provide APIs for checking network availability (I talked about some of the Android network APIs in this discussion of network-based locates) but those APIs don’t tell the whole story.

Often times when working on a mobile device, the Network APIs will report that all is well but a given network call fails anyway (can’t reach host, error reading return value, etc.) … in these cases, often a simple retry will result in a successful call. This can happen anytime with any method call that relies on the network.

What that means is that we need to surround every network call with exception handling and retry logic. With that being the case, a simple call like this…

var byteArray = await 
  new HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif");

Has to get quite a bit more involved. To make the above call automatically retry in the event of a failure … that simple line of code becomes something like this…

const int MaxTries = 2;
bool success = false;
int tryCnt = 0;
byte[] byteArray;
while(!success && tryCnt++ < MaxTries)
{
  try
  {
    byteArray = await 
      new HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif");
    success = true;
  }
  catch(Exception ex)
  {
    if(tryCnt < MaxTries)
      Debug.WriteLine("Retrying after error: " ex.Message);
    else
      Debug.WriteLine("Not retrying -  error:" + ex.Message);
  }
}

Basically we’re just wrapping a try/catch block with a retry loop. Nothing incredibly difficult but a lot of extra code to write around every bit of code that interacts with the network.

To make our lives a lot easier, we can leverage the fact that most of the methods that interact with the network in Xamarin/.NET are asynchronous methods that return Task<T> values. This allows us to create a reusable solution with a helper method that accepts a Func<Task<T>> and returns a Task<T>. That helper method handles all of the details of automatically re-executing the call in the event of a failure.

Such a method would allow us to have our network-based work automatically retried just by writing the following…

var byteArray = await TaskHelper.RunWithRetry(() =>
  { return new HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif"); }

This wraps the work we want to retry in a lambda expression and passes it to the RunWithRetry helper method that encapsulates the retry details.

So now the question is … what does RunWithRetry look like?

The answer to that is the meat of my post today 🙂

Thinking in terms of Tasks

Looking at how to create a reusable solution, I find it’s a bit easier to think in terms of working with the Task<T> capabilities directly rather than using the await behavior in the original retry code I wrote above (await kind’a hides some of the Task<T> details).

Using the Task<T> directly, the code to retry the work in the event of failure might look something like this.

byte[] byteArray = null;
HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif")
  .ContinueWith(firstTask =>
{
  if(firstTask.IsFaulted)
  {
    // First try didn't work, so try again
    HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif")
     .ContinueWith(secondTask =>
    {
      if(secondTask.IsFaulted)
      {
        // Second try didn't work either      
        // Log error and give up
      }
      else
      {
        // Success on second try
        byteArray = secondTask.Result;
      }
    }
  else
  {
    // Success on first try
    byteArray = firstTask.Result;
  }
}

Now that code is even more complicated than the first bit I wrote but we’re beginning to see an opportunity for reusability. Using the Task<T> directly we’re able to determine that the process failed without a try/catch block and could then just repeat the same work we had tried earlier.

Creating a Reusable Solution

We can do some things that makes it reasonably easy to create a reusable solution:

  • Use a Func<Task<T>> to represent the work; this will make our solution generic
  • Use recursion to repeat the work; this will make it easy to retry multiple times without repeating code

Our solution will be composed of two methods. The first is the one that we will call from within our app code.

public static Task<T> RunWithRetry<T>(Func<Task<T>> func, int maxTries = 3)
{
  return RunWithRetryInternal(func, maxTries - 1,
    new TaskCompletionSource<T>(), Enumerable.Empty<Exception>());
}

This method accepts the function to execute (func) and the number of times to try (maxTries) with a default of 3. The job of this method is to setup the actual work of trying the “func” and pass that into another method that will handle the details.

Here’s the function that handles the details.

private static Task<T> RunWithRetryInternal<T>(
  Func<Task<T>> func, int remainingTries,
  TaskCompletionSource<T> tcs, 
  IEnumerable<Exception> previousExceptions)
{
  func().ContinueWith(previousTask =>
  {
    if (previousTask.IsFaulted)
    {
      var exceptions = previousExceptions.Concat(
        previousTask.Exception.Flatten().InnerExceptions);
      if (remainingTries > 0)
        RunWithRetryInternal(func, remainingTries - 1, 
          tcs, exceptions);
      else
        tcs.SetException(exceptions);
    }
    else
      tcs.SetResult(previousTask.Result);
  }, TaskContinuationOptions.ExecuteSynchronously);
  return tcs.Task;
}

This method executes the actual work and then checks to see how things went. If the work encountered an error, the function adds the exceptions to a collection and retries the work by recursively calling itself as long as the max number of tries has not been reached. If the max number of tries is reached, then we associate the exceptions with our TaskCompletionSource and exit (the TaskCompletionSource makes us look like a regular task completion to the caller).

If the work does complete successfully (on any of the tries) we then indicate success through the TaskCompletionSource.

And now our life is simpler

Admittedly that code might be a bit to grock at first but we’re basically just creating a generic way to retry Task-based operations. And once we have that method in place, the code you have to write is pretty simple (this is the RunWithRetry call I showed earlier).

var byteArray = await TaskHelper.RunWithRetry(() =>
  { return new HttpClient().GetByteArrayAsync("https://jwhh.com/HedgeHog.gif"); }

More importantly, you’ll find that your application support life gets a lot simpler.

At Spectafy, we experienced a significant drop in the number of network-based errors that we’re reported to the program by adding this type of retry code. This, in turn, creates a far more stable app, happier users, and simpler app support life.

Posted by hedgehogjim

30+ years as a professional software dev, 17+ years developing mobile solutions, Pluralsight Author, Founder JWHH, LLC

2 Comments

  1. […] Dealing with Spotty Network Coverage in Xamarin Jim shares some code and strategy for dealing with network coverage, which results in happier users and less exceptions […]

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s