I am trying to create a solution with polly where I request an other api.
I have a list of URLs to multiple instances of the same service.
I want that when the first request failes, an other should automaticly start with the next url from my list.
Here is an example where i try this behaviour with two static addresses
The Problem with this solution is that the url does not change until i start the next request.
I want that the urls changes on every retry
public static void ConfigureUserServiceClient(this IServiceCollection services)
{
_userServiceUri = new Uri("https://localhost:5001");
services.AddHttpClient("someService", client =>
{
client.BaseAddress = _userServiceUri;
client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddPolicyHandler(retryPolicy());
}
private static IAsyncPolicy<HttpResponseMessage> retryPolicy()
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt),
onRetry: (result, span, ctx) =>
{
_userServiceUri = new Uri("https://localhost:5002");
});
}
You should consider to use the Fallback policy instead.
Like this:
private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
var addressIterator = GetUrls().GetEnumerator();
var retryLikePolicy = Policy<string>
.Handle<HttpRequestException>()
.FallbackAsync(fallbackAction: async (ct) =>
{
if (addressIterator.MoveNext())
return await GetData(addressIterator.Current);
return null;
});
addressIterator.MoveNext();
var data = await retryLikePolicy.ExecuteAsync(
async () => await GetData(addressIterator.Current));
Console.WriteLine("End");
}
static async Task<string> GetData(string uri)
{
Console.WriteLine(uri);
var response = await client.GetAsync(uri);
return await response.Content.ReadAsStringAsync();
}
static IEnumerable<string> GetUrls()
{
yield return "http://localhost:5500/index.html";
yield return "http://localhost:5600/index.html";
yield return "http://localhost:5700/index.html";
}
Please note that this code is just for demonstration.
UPDATE #1: Multiple fallback
If you have more than one fallback urls then you can alter the above code like this:
private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
var retryInCaseOfHRE = Policy
.Handle<HttpRequestException>()
.WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(1));
var response = await retryInCaseOfHRE.ExecuteAsync(
async () => await GetNewAddressAndPerformRequest());
if (response == null)
{
Console.WriteLine("All requests failed");
Environment.Exit(1);
}
Console.WriteLine("End");
}
static IEnumerable<string> GetAddresses()
{
yield return "http://localhost:5500/index.html";
yield return "http://localhost:5600/index.html";
yield return "http://localhost:5700/index.html";
yield return "http://localhost:5800/index.html";
}
static readonly IEnumerator<string> AddressIterator = GetAddresses().GetEnumerator();
static async Task<string> GetNewAddressAndPerformRequest()
=> AddressIterator.MoveNext() ? await GetData(AddressIterator.Current) : null;
static async Task<string> GetData(string uri)
{
Console.WriteLine(uri);
var response = await client.GetAsync(uri);
return await response.Content.ReadAsStringAsync();
}
The trick: the retry policy wraps a method which is responsible to retrieve the next url and then call the GetData
In other word we need to move the iteration process into the to be wrapped method (GetNewAddressAndPerformRequest)
I've replaced the Fallback policy to Retry since we need to perform (potentially) more than 1 fallback actions
I've used null to indicate we have run out of fallback urls but it might be a better solution to use a custom exception for that
Related
I am upgrading a Xamarin app to MAUI and thought of decoupling things a bit. Before i had a datastore which handled all requests to an API, now i have a service for each section of the app from which requests go to a HttpManager, problem is when the policy retries, it works for the first time but on the second retry it fails with the message "Cannot access a closed Stream". Searched a bit but couldn't find a fix.
I call the service from the viewModel.
LoginViewModel.cs
readonly IAuthService _authService;
public LoginViewModel(IAuthService authService)
{
_authService = authService;
}
[RelayCommand]
private async Task Login()
{
...
var loginResponse = await _authService.Login(
new LoginDTO(QRSettings.StaffCode, Password, QRSettings.Token));
...
}
In the service i set send the data to the HttpManager and process the response
AuthService.cs
private readonly IHttpManager _httpManager;
public AuthService(IHttpManager manager)
{
_httpManager = manager;
}
public async Task<ServiceResponse<string>> Login(LoginDTO model)
{
var json = JsonConvert.SerializeObject(model);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpManager.PostAsync<string>("Auth/Login", content);
...
}
And in here i send the request.
HttpManager.cs
readonly IConnectivity _connectivity;
readonly AsyncPolicyWrap _retryPolicy = Policy
.Handle<TimeoutRejectedException>()
.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1), (exception, timespan, retryAttempt, context) =>
{
App.AppViewModel.RetryTextVisible = true;
App.AppViewModel.RetryText = $"Attempt number {retryAttempt}...";
})
.WrapAsync(Policy.TimeoutAsync(11, TimeoutStrategy.Pessimistic));
HttpClient HttpClient;
public HttpManager(IConnectivity connectivity)
{
_connectivity = connectivity;
HttpClient = new HttpClient();
}
public async Task<ServiceResponse<T>> PostAsync<T>(string endpoint, HttpContent content, bool shouldRetry = true)
{
...
// Post request
var response = await Post($""http://10.0.2.2:5122/{endpoint}", content, shouldRetry);
...
}
async Task<HttpResponseMessage> Post(string url, HttpContent content, bool shouldRetry)
{
if (shouldRetry)
{
// This is where the error occurs, in the PostAsync
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, content, token), CancellationToken.None);
...
}
...
}
And this is the MauiProgram if it matters
...
private static MauiAppBuilder RegisterServices(this MauiAppBuilder builder)
{
...
builder.Services.AddSingleton<IHttpManager, HttpManager>();
builder.Services.AddSingleton<IAuthService, AuthService>();
return builder;
}
Can't figure out what the issue is...
I tried various try/catches, tried finding a solution online but no luck.
On the second retry it always gives that error
Disclaimer: In the comments section I've suggested to rewind the underlying stream. That suggestion was wrong, let me correct myself.
TL;DR: You can't reuse a HttpContent object you need to re-create it.
In order to be able to perform a retry attempt with a POST verb you need to recreate the HttpContent payload for each attempt.
There are several ways to fix your code:
Pass the serialized string as parameter
async Task<HttpResponseMessage> Post(string url, string content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"), token), CancellationToken.None);
...
}
...
}
Pass the to-be-serialized object as parameter
async Task<HttpResponseMessage> Post(string url, object content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, JsonContent.Create(content), token), CancellationToken.None);
...
}
...
}
Here we are taking advantage of the JsonContent type which was introduced in .NET 5
Pass the to-be-serialized object as parameter #2
async Task<HttpResponseMessage> Post(string url, object content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsJsonAsync(url, content, token), CancellationToken.None);
...
}
...
}
Here we are taking advantage of an extension method called PostAsJsonAsync
It was introduced under the HttpClientExtensions
But nowadays it resides inside the HttpClientJsonExtensions
I have a method that returns some value based on an API call, this API limits the amount of calls that you can do per period of time. I need to access the results of this call from multiple threads. Right now i have the following code:
class ReturningSemaphoreLocker<TOutput>
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async Task<T> LockAsync<T>(Func<Task<T>> worker)
{
await _semaphore.WaitAsync();
try
{
return await worker();
}
finally
{
_semaphore.Release();
}
}
}
Usage example:
...
private static readonly ReturningSemaphoreLocker<List<int>> LockingSemaphore = new ReturningSemaphoreLocker<List<int>>();
...
public async Task<List<int>> GetStuff()
{
return await LockingSemaphore.LockAsync(async () =>
{
var client = _clientFactory.CreateClient("SomeName");
using (var cts = GetDefaultRequestCts())
{
var resp = await client.GetAsync("API TO QUERY URL", cts.Token);
var jsonString = await resp.Content.ReadAsStringAsync();
var items = JsonConvert.DeserializeObject<List<int>>(jsonString);
return items;
}
});
}
So the question is: how do i get the same result from GetStuff() if it's already running WITHOUT querying the API again and query the API again if the method is not running at this very moment?
The trick here is to hold onto the Task<T> that is the incomplete result; consider the following completely untested approach - the _inProgress field is the key here:
private static readonly ReturningSemaphoreLocker<List<int>> LockingSemaphore = new ReturningSemaphoreLocker<List<int>>();
...
private Task<List<int>> _inProgress;
public Task<List<int>> GetStuffAsync()
{
if (_inProgress != null) return _inProgress;
return _inProgress = GetStuffImplAsync();
}
private async Task<List<int>> GetStuffImplAsync()
{
var result = await LockingSemaphore.LockAsync(async () =>
{
var client = _clientFactory.CreateClient("SomeName");
using (var cts = GetDefaultRequestCts())
{
var resp = await client.GetAsync("API TO QUERY URL", cts.Token);
var jsonString = await resp.Content.ReadAsStringAsync();
var items = JsonConvert.DeserializeObject<List<int>>(jsonString);
return items;
}
});
// this is important so that if everything turns
// out to be synchronous, we don't nuke the _inProgress field *before*
// it has actually been set
await Task.Yield();
// and now wipe the field since we know it is no longer in progress;
// the next caller should actually try to do something interesting
_inProgress = null;
return result;
}
Here is a class that you could use for time-based throttling, instead of the ReturningSemaphoreLocker:
class ThrottledOperation
{
private readonly object _locker = new object();
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private Task _task;
public Task<T> GetValueAsync<T>(Func<Task<T>> taskFactory, TimeSpan interval)
{
lock (_locker)
{
if (_task != null && (_stopwatch.Elapsed < interval || !_task.IsCompleted))
{
return (Task<T>)_task;
}
_task = taskFactory();
_stopwatch.Restart();
return (Task<T>)_task;
}
}
}
The GetValueAsync method returns the same task, until the throttling interval has been elapsed and the task has been completed. At that point it creates and returns a new task, using the supplied task-factory method.
Usage example:
private static readonly ThrottledOperation _throttledStuff = new ThrottledOperation();
public Task<List<int>> GetStuffAsync()
{
return _throttledStuff.GetValueAsync(async () =>
{
var client = _clientFactory.CreateClient("SomeName");
using (var cts = GetDefaultRequestCts())
{
var resp = await client.GetAsync("API TO QUERY URL", cts.Token);
var jsonString = await resp.Content.ReadAsStringAsync();
var items = JsonConvert.DeserializeObject<List<int>>(jsonString);
return items;
}
}, TimeSpan.FromSeconds(30));
}
I have 2 Entity and I want to copy some data from Second Entity to First Entity and after that I want to return a simple string saying Success.I am using Polly to make http request.I am planning to get data in json and then convert it in my Entity model and do the manipulation which I am able to do but Calling both the task which return differnt types(can be slight different data model) giving some error.I am not so good in Multithreading approach.
public interface IMyRepository
{
string ValidateData(MyData myData);
}
public class MyRepository :IMyRepository
{ private readonly RetryPolicy<HttpResponseMessage> _httpRequestPolicy;
public MyRepository()
{
_httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(retryAttempt), (exception, timeSpan, retryCount, context1) =>
{
var msg = $"Retry {retryCount} implemented with Pollys RetryPolicy " +
$"of {context1.PolicyKey} " +
$"at {context1.ExecutionKey}, " +
$"due to: {exception}.";
});
}
public string ValidateData(MyData MyData)
{
var MyDataOne= Task<MyData>.Factory.StartNew(() => await MyRepository.getProfileOne());
var MyDataTwo= Task<MyData>.Factory.StartNew(() => await MyRepository.getProfileTwo());
//Update some property of MyDataOne on basis of MyDataTwo and return true or fasle in variable **result**
return result;
}
public static async Task<InsuranceCompanyData> getCusomerProfile()
{
var httpClient = GetHttpClient();
string requestEndpoint = "numbers/Get";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
IEnumerable<int> numbers = await httpResponse.Content.ReadAsAsync<IEnumerable<int>>();
return new InsuranceCompanyData();
}
private static HttpClient GetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:2351/api/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return httpClient;
}
}
public static async Task<MyData> getProfileOne()
{
var httpClient = GetHttpClient();
string requestEndpoint = "/numbers/Get1";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
// IEnumerable<string> data1= await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();
return new MyData();
}
public static async Task<MyData> getProfileTwo()
{
var httpClient = GetHttpClient();
string requestEndpoint = "/numbers/Get2";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
// IEnumerable<string> data2= await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();
return new MyyData();
}
private static HttpClient GetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:2351/api/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return httpClient;
}
I get these errors:
The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.
And
An object reference is required for the non-static field, method, or property 'MyRepository._httpRequestPolicy'
Instead of using Task.Factory.StartNew which is not recommended and doesn't support async lambdas (which you don't even need here), use Task.Run:
var profileOneTask = Task.Run(() => getProfileOne());
var profileTwoTask = Task.Run(() => getProfileTwo());
Notice that I changed the variable names to reflect what they actually are. They are tasks that may, at some point, have a result. They are not the result of those operations.
For the second problem, you declared _httpRequestPolicy as an instance member when you should have declared it as a static member for it to be usable without an instance. As discussed in the comments, though, you could just make getProfileOne and getProfileTwo instance methods.
Why you don't change ValidateData signature and add async keyword to method ?
public async Task<string> ValidateDataAsync(MyData MyData)
{
var task1 = Task<MyData>.Factory.StartNew(() => MyRepository.getProfileOne());
var task2 = Task<MyData>.Factory.StartNew(() => MyRepository.getProfileTwo());
await Task.WhenAll(task1, task2)
//Update some property of MyDataOne on basis of MyDataTwo and return true or fasle in variable **result**
return result;
}
As #Camilo Terevinto said it's better to use Task.Run instead of TaskFactory.StartNew.
Task.Run vs Task.Factory.StartNew
I am trying to trace a chain of redirects (for an online ad pixel) programmatically, but with a timeout of 2 seconds (in other words, if the redirect chain takes more than 2 seconds to resolve, I want it to abort and return null).
My code is (more or less) running synchronously, so I had to do some acrobatics to do what I wanted, but functionally speaking, it seems to work... except for the timeout part.
I have some asynchronous helpers like so:
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
{
using (var timeoutCancellationTokenSource = new CancellationTokenSource())
{
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
if (completedTask != task)
{
throw new TimeoutException();
}
timeoutCancellationTokenSource.Cancel();
return await task;
}
}
public static T ToSynchronousResult<T>(this Task<T> task)
{
return Task.Run(async () => await task).Result;
}
The TimeoutAfter() helper method was adapted from the SO article that can be found here. In my service I have a method that resembles this:
public string GetFinalUrl(string url)
{
string finalUrl;
try
{
finalUrl = FollowDestinationUrl(url).TimeoutAfter(TimeSpan.FromSeconds(2)).ToSynchronousResult();
}
catch (TimeoutException)
{
finalUrl = null;
}
return finalUrl;
}
private async Task<string> FollowDestinationUrl(string url)
{
var request = _webRequestFactory.CreateGet(url);
var payload = await request.GetResponseAsync();
return payload.ResponseUri.ToString();
}
The _webRequestFactory here returns an HttpWebRequest abstraction that was written as an IHttpRequest.
In my success case unit test (response under 2 seconds), I get back the result I expect:
private class TestWebResponse : WebResponse
{
public override Uri ResponseUri => new Uri("https://www.mytest.com/responseIsGood");
}
[TestMethod]
public void RedirectUriUnderTimeout()
{
//arrange
var service = GetService();
A.CallTo(() => _httpRequest.GetResponseAsync()).ReturnsLazily(() => new TestWebResponse());
A.CallTo(() => _httpRequest.GetResponseString())
.ReturnsLazily(() => VALID_REQUEST_PAYLOAD);
//act
var url = service.GetFinalUrl("https://someplace.com/testurl");
//assert
Assert.IsNotNull(url);
}
...however, when I try to implement a delay to verify the timeout is working correctly, it's not aborting as I would expect:
[TestMethod]
public void RedirectUriUnderTimeout()
{
//arrange
var service = GetService();
A.CallTo(() => _httpRequest.GetResponseAsync()).ReturnsLazily(() => {
Thread.Sleep(TimeSpan.FromSeconds(3));
return new TestWebResponse();
});
A.CallTo(() => _httpRequest.GetResponseString())
.ReturnsLazily(() => VALID_REQUEST_PAYLOAD);
//act
var url = service.GetFinalUrl("https://someplace.com/testurl");
//assert
Assert.IsNull(url);
}
It seems like it waits for the full three seconds, before returning the TestWebResponse that has a non-null ResponseUri.
I don't know if there's something fundamentally wrong with my implementation, or wrong with my test, but obviously I'm blocking an async call in a way I'm not expecting to.
Can someone help me identify what I've done wrong?
public static T ToSynchronousResult<T>(this Task<T> task)
{
return Task.Run(async () => await task).Result;
}
This part causes to get thread blocked.As you mentioned the method ToSynchronousResult, it will block the thread until task result returned. You should follow "async all the way" rule and you should use await. It is only way to apply async efficiently.
public async Task<string> GetFinalUrl(string url)
{
string finalUrl;
try
{
finalUrl = await FollowDestinationUrl(url).TimeoutAfter(TimeSpan.FromSeconds(2));
}
catch (TimeoutException)
{
finalUrl = null;
}
return finalUrl;
}
OK, it looks like I was way overthinking it. #Stormcloak clued me in that what I was doing wasn't going to work, so I started looking at alternatives, and I realized that while the async/ await pattern weren't appropriate here, the TPL library still came in handy.
I changed my FinalDestinationUrl method to synchronous like so:
private string FollowDestinationUrl(string url)
{
var request = _webRequestFactory.CreateGet(url);
var payload = request.GetResponse();
return payload.ResponseUri.ToString();
}
then I called it like so:
var task = Task.Run(() => FollowDestinationUrl(destinationUrl));
finalUrl = task.Wait(TimeSpan.FromSeconds(2)) ? task.Result : null;
Then I changed my unit test to resemble:
[TestMethod]
public void RedirectUriUnderTimeout()
{
//arrange
var service = GetService();
A.CallTo(() => _httpRequest.GetResponse()).ReturnsLazily(() => {
Thread.Sleep(TimeSpan.FromSeconds(3));
return new TestWebResponse();
});
A.CallTo(() => _httpRequest.GetResponseString())
.ReturnsLazily(() => VALID_REQUEST_PAYLOAD);
//act
var url = service.GetFinalUrl("https://someplace.com/testurl");
//assert
Assert.IsNull(url);
}
The test passed. All is well in the world. Thanks!
I am trying to use Func with Async Method. And I am getting an error.
Cannot convert async lambda expression to delegate type 'Func<HttpResponseMesage>'. An async lambda expression may return void, Task or Task<T>, none of which are convertible to 'Func<HttpResponseMesage>'.
below is my Code:
public async Task<HttpResponseMessage> CallAsyncMethod()
{
Console.WriteLine("Calling Youtube");
HttpClient client = new HttpClient();
var response = await client.GetAsync("https://www.youtube.com/watch?v=_OBlgSz8sSM");
Console.WriteLine("Got Response from youtube");
return response;
}
static void Main(string[] args)
{
Program p = new Program();
Task<HttpResponseMessage> myTask = p.CallAsyncMethod();
Func<HttpResponseMessage> myFun =async () => await myTask;
Console.ReadLine();
}
As the error says, async methods return Task,Task<T> or void. So to get this to work you can:
Func<Task<HttpResponseMessage>> myFun = async () => await myTask;
The path I usually take is to have the Main method invoke a Run() method that returns a Task, and .Wait() on the Task to complete.
class Program
{
public static async Task<HttpResponseMessage> CallAsyncMethod()
{
Console.WriteLine("Calling Youtube");
HttpClient client = new HttpClient();
var response = await client.GetAsync("https://www.youtube.com/watch?v=_OBlgSz8sSM");
Console.WriteLine("Got Response from youtube");
return response;
}
private static async Task Run()
{
HttpResponseMessage response = await CallAsyncMethod();
Console.ReadLine();
}
static void Main(string[] args)
{
Run().Wait();
}
}
This allows the rest of your Console app to run with full async/await support. Since there isn't any UI thread in a console app, you don't run the risk of deadlocking with the usage of .Wait().
Code fix such as:
static void Main(string[] args)
{
Program p = new Program();
Task<HttpResponseMessage> myTask = p.CallAsyncMethod();
Func<Task<HttpResponseMessage>> myFun = async () => await myTask;
Console.ReadLine();
}
Inside the Func run the task, wait for it and check for exception, then return the result.
Func<HttpResponseMessage> myFun = () =>
{
var t = Task.Run(async () => await myTask);
t.Wait();
if (t.IsFaulted)
throw t.Exception;
return t.Result;
};