2
\$\begingroup\$

I have several extension methods for interacting with the Google Calendar API, and these all use an ExecuteWithRetry function for wrapping API calls within retry logic:

private static readonly int maxAttempts = 4;
private static readonly int retryFirst = 5000;
private static readonly int retryExponent = 4;

private static async Task<TResponse> ExecuteWithRetryAsync<TResponse>(Func<Task<TResponse>> func)
{
  TResponse response = default;
  for (var attempt = 1; attempt <= maxAttempts; attempt++)
  {
    try
    {
      response = await func();
      break;
    }
    catch (Google.GoogleApiException) when (attempt < maxAttempts)
    {
      var backoff = retryFirst * (int)Math.Pow(retryExponent, attempt - 1);
      await Task.Delay(backoff);
    }
  }
  return response;
}

public static async Task<IList<Event>> FetchAllWithRetryAsync(this EventsResource.ListRequest listRequest, DateTime? after = null, DateTime? before = null)
{
  listRequest.TimeMin = after;
  listRequest.TimeMax = before;
  var pageStreamer = new PageStreamer<Event, EventsResource.ListRequest, Events, string>(
    (request, token) => request.PageToken = token,
    response => response.NextPageToken,
    response => response.Items
  );
  return await ExecuteWithRetryAsync(() => pageStreamer.FetchAllAsync(listRequest, CancellationToken.None));
}

public static async Task<TResponse> ExecuteWithRetryAsync<TResponse>(this IClientServiceRequest<TResponse> request)
  => await ExecuteWithRetryAsync(() => request.ExecuteAsync());

public static async Task ExecuteWithRetryAsync(this BatchRequest request)
  => await ExecuteWithRetryAsync(async () => { await request.ExecuteAsync(); return Task.FromResult(0); });

General feedback welcome.

I'm particularly uneasy about the last function. Basically BatchRequest.ExecuteAsync() returns a Task rather than a Task<TResponse>, so I'm using

async () => { await request.ExecuteAsync(); return Task.FromResult(0); }

for it to be accepted as a Func<Task<TResponse>>. Is there a cleaner way of doing this?

\$\endgroup\$
  • \$\begingroup\$ Unfortunately this doesn't solve the annoying problem you have, but at least you can return 0 and not Task.FromResult(0). \$\endgroup\$ – Stefano d'Antonio Oct 25 '17 at 11:08
  • \$\begingroup\$ To make it slightly nicer you could return a constant MyVoid type (like unit), but I'm not sure that's better. \$\endgroup\$ – Stefano d'Antonio Oct 25 '17 at 11:10
  • \$\begingroup\$ @Stefanod'Antonio Good point about returning 0! \$\endgroup\$ – James Oct 25 '17 at 11:55
  • \$\begingroup\$ You should not await a single async method in another async method. You can simply return the Task directly, i.e. public static Task<TResponse> ExecuteWithRetryAsync<TResponse>(...) => ExecuteWithRetryAsync(() => request.ExecuteAsync()); \$\endgroup\$ – Chris Pratt Oct 27 '17 at 15:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.