I'm pretty new to async/await and I want to ensure that the code I've written is going to be safe and not cause any deadlocks.
The RestClient
in the code will be injected as a singleton dependency via an IoC within an ASP.NET MVC application, so that the access token request is made only once and shared across all API requests within the application.
I'm using Polly as a retry manager to handle expired access tokens - I'm not sure if this is the best approach, but it seems like a good fit right now.
I've simplified the code example somewhat, just to keep things a bit more readable.
I'm really interested in taking on more async code, so I'd be very happy if anyone finds any potential issues with the code or suggestions on better practices.
public class RestClient {
private TokenResponse _token;
private const int RetryCount = 2;
private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1);
public async Task<T> MakeRequestAsync<T>(string uri) where T : class {
return await Policy
.Handle<InvalidAccessTokenException>()
.RetryAsync(RetryCount, (exception, i) => InvalidateToken())
.ExecuteAsync(async () => await ExecuteRequestAsync<T>(uri));
}
private async Task<TokenResponse> GetTokenAsync() {
try {
await _syncLock.WaitAsync().ConfigureAwait(false);
if (_token == null) {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(ProviderBaseUrl);
var response = await client.PostAsync(TokenEndpoint, new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_id", ClientId),
new KeyValuePair<string, string>("client_secret", ClientSecret),
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("password", Password)
})).ConfigureAwait(false);
_token = await response.Content.ReadAsAsync<TokenResponse>().ConfigureAwait(false);
}
}
}
finally {
_syncLock.Release();
}
return _token;
}
private async Task<T> ExecuteRequestAsync<T>(string uri) where T : class
{
var token = await GetTokenAsync();
if (token.access_token == null) {
return null;
}
using (var client = new HttpClient()) {
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token.access_token);
var response = await client.GetAsync(new Uri(String.Format("{0}{1}", ProviderBaseUrl, uri))).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.Unauthorized) {
throw new InvalidAccessTokenException();
}
return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
}
}
private void InvalidateToken()
{
_token = null;
}
}