Just can't get it working. All I'm trying to do is timeout a http get request if it takes longer than 400ms and and get it to retry 5 times.
I've put a Task Delay into my code so it should time out, but I just get a success.
Using .Net Standard 2.0
private async Task<AwesomeObject<T>> GetDataAsync<T>(string query)
{
HttpResponseMessage response = null;
var timeOutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromMilliseconds(400),
ManageTimeoutException);
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError() // HttpRequestException, 5XX and 408
.Or<TimeoutRejectedException>() // RetryAfter
.RetryAsync(5, ManageRetryException);
AsyncPolicyWrap<HttpResponseMessage> policyWrap = timeOutPolicy.WrapAsync(retryPolicy);
using (var client = new HttpClient())
{
ConfigureRequest(client);
await policyWrap.ExecuteAsync(async () =>
{
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
await Task.Delay(TimeSpan.FromMilliseconds(1000));
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
Debug.WriteLine("ExecuteAsync...");
return response = await client.GetAsync(new Uri(_url, query));
}
);
response.EnsureSuccessStatusCode();
}
if (response.IsSuccessStatusCode == false)
{
return null;
}
var resultJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<AwesomeObject<T>>(resultJson);
}
private static Task ManageTimeoutException(Context context, TimeSpan timeSpan, Task task)
{
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
var msg = $"The execution timed out after {timeSpan.TotalSeconds} seconds, eventually terminated with: {t.Exception}.";
Debug.WriteLine(msg);
}
else if (t.IsCanceled)
{
var msg = $"The execution timed out after {timeSpan.TotalSeconds} seconds, task cancelled.";
Debug.WriteLine(msg);
}
});
return task;
}
private static void ManageRetryException(DelegateResult<HttpResponseMessage> responseMessage, int retryCount)
{
var msg = $"Retry n掳{retryCount} : {responseMessage.Exception.Message}";
Debug.WriteLine(msg);
}
Hi @cmadhani . TimeoutPolicy in TimeoutStrategy.Optimistic (the default) operates by co-operative cancellation, so delegates you execute through the policy must take and honour a CancellationToken.
You can see demonstration code examples for this case (HttpClient with TimeoutPolicy) in the ReadMe on TimeoutPolicy and wiki for TimeoutPolicy. These also explain TimeoutPolicy's default use of CancellationToken in more detail.
For your code:
await policyWrap.ExecuteAsync(async () =>
{
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
await Task.Delay(TimeSpan.FromMilliseconds(1000));
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
Debug.WriteLine("ExecuteAsync...");
return response = await client.GetAsync(new Uri(_url, query));
});
Instead, use:
await policyWrap.ExecuteAsync(async ct =>
{
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
await Task.Delay(TimeSpan.FromMilliseconds(1000), ct);
Debug.WriteLine($"{DateTime.Now}:{DateTime.Now.Millisecond}");
Debug.WriteLine("ExecuteAsync...");
return response = await client.GetAsync(new Uri(_url, query), ct);
},
CancellationToken.None // This token represents independent user cancellation - if you have any. If not, just use CancellationToken.None
);
To time out delegates which _don't_ honour cancellation (such as the delegates in your original code example), you can use TimeoutStrategy.Pessimistic. But that is not recommended with HttpClient, because HttpClient does provide overloads taking CancellationToken, and TimeoutStrategy.Pessimistic consumes more resource.
Please let me know if there is anything else we can help with.
Hi @cmadhani . If you haven't seen it already, check out as well our specific doco on the different effects of combining retry with timeout in different orders, in PolicyWrap.
With the order you have:
AsyncPolicyWrap<HttpResponseMessage> policyWrap = timeOutPolicy.WrapAsync(retryPolicy);
the timeout will act as a timeout across _all tries combined_. IE Once the timeout occurs, no further tries will be permitted - they will be pre-emptively cancelled by the timeout's CancellationToken which has signalled (fired).
If you want to use TimeoutPolicy as a timeout-per-try, reverse the order of the policies:
AsyncPolicyWrap<HttpResponseMessage> policyWrap = retryPolicy.WrapAsync(timeOutPolicy);
Again, more on that in the doco already linked, and here.
Closing as no further qs raised and the doco/commentary covers the original issue. However, if you do have further questions, please just post again (or start a new issue), and we will gladly pick up the conversation. Thanks.
Sorry for the delay in reponding. This has answered my questions. Thank you.
Most helpful comment
Sorry for the delay in reponding. This has answered my questions. Thank you.