I was feeling like I had quite a good grip on async await programming, but what happened today made me perplexed. I have been searching for an answer to this for a little while now, but was not able to find it anywhere. I have read quite a bit about async await programming, but my feeble mind is not capable of understanding what exactly is happening in this particular scenario.
I have these two methods:
public async Task<IEnumerable<ServerStatus>> GetServersStatusAsync()
{
var serverStatuses = new List<ServerStatus>();
try
{
ServerStatusRequest request = new ServerStatusRequest();
var serverStatusResponse = await GetAsync<ServerStatusResponse>(request).ConfigureAwait(false);
}
// I would expect the exception thrown from GetAsync to be caught here. But it doesn't always do that.
catch (Exception ex)
{
_log.Error("Failed to retrieve server statuses.", ex);
}
return serverStatuses;
}
And
private async Task<T> GetAsync<T>(IRequest request)
{
string respString = string.Empty;
try
{
var requestUrl = request.GetQueryString(_apiToken);
_log.Debug($"Sending a request to {requestUrl}");
CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutInSec));
//This call can throw an exception. It will always get caught inside this current try catch,
//but if after the await we are continuing on a different thread,
//the re-thrown/propagated exception is not caught by the calling method.
var response = await _client.GetAsync(requestUrl, tokenSource.Token).ConfigureAwait(false);
if (response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.BadRequest)
{
respString = await response.Content.ReadAsStringAsync();
var deserializedObject = JsonConvert.DeserializeObject<T>(respString);
return deserializedObject;
}
}
catch (Exception ex) when (ex is JsonReaderException || ex is JsonSerializationException)
{
throw new JsonSerializationException($"Failed to deserialize {respString}", ex) { Source = respString };
}
return default(T);
}
I have added 2 comments to the code snippets as pointers, but the basic idea is:
In GetServerStatusAsync I have wrapped everything with a try catch as I want to handle the exceptions there. I call GetAsync which awaits a call to HttpClient.GetAsync with ConfigureAwait(false). When the call from HttpClient.GetAsync returns with an exception if we are no longer on the initial thread/context, it can be caught inside my GetAsync method, but will not get propagated to GetServerStatusAsync. If I remove ConfigureAwait(false), it always propagates down as I would expect, but with ConfigureAwait(false) it's more of a 50/50.
Is there something catastrophically wrong with my code or my understanding of async await?
Any help is much appreciated.
Edit: As per request in the comments I'm adding a simplified version of the method that calls GetServersStatusAsync and how I call that method (in a fire and forget fashion, but Login is wrapped with a try catch so that shouldn't be a big issue).
public async Task Login(Action<LoginResult> callback = null)
{
LoginResult result = new LoginResult() { Success = false };
try
{
var serverStatus = await GetServersStatusAsync();
if (serverStatus.Any())
{
result.Success = true;
callback?.Invoke(result);
return;
}
}
catch (Exception ex)
{
result.Message = Strings.UnknownException;
_log.Error("Login failed due to unexpected exception", ex);
}
callback?.Invoke(result);
}
_restClient.Login(OnLoginResponse);