I creating a list of Tasks and executing them with Task.WhenAll(). If an http request returns error status I want to retry the call.
I'm getting and error on this line: ExecuteAsync(() => func()));
Cannot implicitly convert type 'System.Threading.Tasks.Task' to'System.Threading.Tasks.Task<System.Net.HttpWebResponse>'
I know what the error means, but the part I need help with is the overall implementation of Polly with parallel http calls and checking the response status.
Code:
var tasks = new List<Task<string>>();
var policy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpWebResponse>(a => a.StatusCode != HttpStatusCode.OK)
.WaitAndRetryForeverAsync(i => pauseBetweenFailures);
var urls = new List<string>();
foreach (var mediaItem in UploadedMediaItems)
{
var mediaRequest = new HttpRequestMessage
{
RequestUri = new Uri("****"),
Method = HttpMethod.Get,
Headers = {
{ "id-token", id_Token },
{ "access-token", access_Token }
}
};
async Task<string> func()
{
var response = await client.SendAsync(mediaRequest);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(policy.ExecuteAsync(() => func()));
}
await Task.WhenAll(tasks);
foreach (var t in tasks)
{
var postResponse = await t;
urls.Add(postResponse);
}
Disregarding any other issues (conceptual or otherwise)
You have the wrong generic parameter HttpWebResponse, it should be HttpResponseMessage as that is what SendAsync returns
var policy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(a => a.StatusCode != HttpStatusCode.OK)
...
Also, seemingly you would want to apply the policy to the SendAsync method, not the local method that returns a Task<string>
async Task<string> func()
{
var response = await policy.ExecuteAsync(() => client.SendAsync(mediaRequest));
return await response.Content.ReadAsStringAsync();
}
tasks.Add(func());
Also note, that you will need to recreate a new HttpRequestMessage for every retry. They cannot be reused.
Which may look more like this
Task<HttpResponseMessage> Send()
{
var mediaRequest = new HttpRequestMessage {... };
return client.SendAsync(mediaRequest);
}
async Task<string> func()
{
var response = await policy.ExecuteAsync(Send);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(func());
However, on saying all this, you would likely want to use the more modern approach of adding Polly to the IHttpClientFactory implementation when you register the service via AddPolicyHandler, which will in turn take care of the request
Related
The case is that I call an API once to get a list of tenants, then for each tenant I must call again to get a list of usages for the tenant. Unfortunately there is no way to get usages for all tenants in a single call.
Now I wish to try to save time by making these calls concurrent. Then put them all together after the last one arrives. Here is what my attempt looks like so far:
public async Task<List<Usage>> GetUsagesAsync2(Task<List<Tenant>> tenants)
{
List<Usage> usages = new List<Usage>();
foreach (var tenant in await tenants)
{
//Generate request
RestRequest request = new RestRequest("tenants/{tenantID}/usages", Method.Get);
request.AddParameter("tenantID", tenant.id, ParameterType.UrlSegment);
request.AddHeader("Authorization", $"Bearer {Token.access_token}");
//Get response
RestResponse response = await _client.ExecuteAsync(request)
.ConfigureAwait(false);
//Validate response
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("Failed at Getting usages for a tenant: ");
//Add to list
var t_usage = JsonConvert.DeserializeObject<Wrapper<Usage>>(response.Content);
usages.AddRange(t_usage.items);
}
return usages;
}
The code runs and does what it is supposed to, but I am not sure that it actually runs asynchronously. Takes about 7-8 seconds to run, which I find a bit long to wait on a webpage.
Here is parallelized implementation:
.NET 6
public async Task<ConcurrentBag<Usage>> GetUsagesAsync2(Task<List<Tenant>> tenants)
{
ConcurrentBag<Usage> usages = new ConcurrentBag<Usage>();
await Parallel.ForEachAsync(await tenants, async (tenant) =>
{
//Generate request
RestRequest request = new RestRequest("tenants/{tenantID}/usages", Method.Get);
request.AddParameter("tenantID", tenant.id, ParameterType.UrlSegment);
request.AddHeader("Authorization", $"Bearer {Token.access_token}");
//Get response
RestResponse response = await _client.ExecuteAsync(request).ConfigureAwait(false);
//Validate response
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("Failed at Getting usages for a tenant: ");
//Add to list
var t_usage = JsonConvert.DeserializeObject<Wrapper<Usage>>(response.Content);
usages.AddRange(t_usage.items);
});
return usages;
}
Pre .Net 6
public async Task<ConcurrentBag<Usage>> GetUsagesAsync2(Task<List<Tenant>> tenants)
{
ConcurrentBag<Usage> usages = new ConcurrentBag<Usage>();
var tasks = new List<Task>();
foreach (var tenant in await tenants)
{
tasks.Add(Task.Run(async () =>
{
//Generate request
RestRequest request = new RestRequest("tenants/{tenantID}/usages", Method.Get);
request.AddParameter("tenantID", tenant.id, ParameterType.UrlSegment);
request.AddHeader("Authorization", $"Bearer {Token.access_token}");
//Get response
RestResponse response = await _client.ExecuteAsync(request)
.ConfigureAwait(false);
//Validate response
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("Failed at Getting usages for a tenant: ");
//Add to list
var t_usage = JsonConvert.DeserializeObject<Wrapper<Usage>>(response.Content);
usages.AddRange(t_usage.items);
}));
}
await Task.WhenAll(tasks);
return usages;
}
Take note that List is changed to ConcurrentBag because it is not thread safe
What you could basically do is:
IEnumerable<Task<List<Usage>> loadTasks = tenants.Select(LoadUsages);
List<Usage>[] usages = await Task.WhenAll(loadTasks);
async Task<List<Usage>> LoadUsages(Tenant t) {
// your web call goes here
return t_usage.items;
}
But, as pointed out in the comments, this will not be throttled and might issue way too many requests at once. If you're sure the number of tenants will stay around 20 this should be fine. Otherwise you'll have to implement a more sophisticated solution that does batch processing.
My code to fetch a huge set of data from an API is like this
public static async Task<model> GetDataAsyncs(string url)
{
// Initialization.
mymodel responseObj = new mymodel();
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
using (var client = new HttpClient(httpClientHandler))
{
// Setting Base address.
client.BaseAddress = new Uri(apiBasicUri);
// Setting content type.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Initialization.
HttpResponseMessage response = new HttpResponseMessage();
// HTTP Get
response = await client.GetAsync(url ).ConfigureAwait(false);
// Verification
if (response.IsSuccessStatusCode)
{
// Reading Response.
string result = response.Content.ReadAsStringAsync().Result;
responseObj.Status = true;
responseObj.Data = result;
}
}
}
return responseObj;
}
And I am calling above function like this inside my controller
public ActionResult myActionMethod()
{
string res= helper.GetDataAsync("url").Result.Data;
}
Ocassionally this throws an error system.threading.tasks.taskcanceledexception a task was canceled . This does not occurs every time. Can anyone please point out what I am doing wrong here?
I can't say for sure why this is happening, but there are some red flags in your code that can be cleaned up and might resolve this.
The first is your use of .ConfigureAwait(false). It can cause some unintended consequences, so I suggest you don't use it. I talk about it more in an article I recently wrote.
Second, use await instead of .Result whenever possible, which is almost always. Using .Result can also cause unintended, hard-to-debug consequences. In your code, I see no reason you can't use await.
Third, the documentation of HttpClient says:
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
So you can declare a static HttpClient and reuse that every time you need it.
Fourth, there's no need for this line:
HttpResponseMessage response = new HttpResponseMessage();
You're instantiating a new HttpResponseMessage here, but then immediately overwriting it in the next line.
Making those changes, your code could look like this:
private static HttpClient _client = new HttpClient(
new HttpClientHandler {ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }}
) {
BaseAddress = new Uri(apiBasicUri),
DefaultRequestHeaders = {
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
public static async Task<model> GetDataAsyncs(string url)
{
mymodel responseObj = new mymodel();
var response = await _client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
responseObj.Status = true;
responseObj.Data = result;
}
return responseObj;
}
And then change your controller action to be async and use await:
public async Task<ActionResult> myActionMethod()
{
var res = (await helper.GetDataAsync("url")).Data;
}
See if you still end up getting exceptions after making those changes.
I'm successfully getting a token back from my GetAccessToken() and GetAccessTokenAsync methods, but the token isn't retrieved until after the main method of GetCourses, which won't work because that's the method that collects the data I need to show on my cshtml page. I've tried pulling apart this controller and creating a Globals class that will house just the URIs, apiKey, and token, but then I read that's bad practice for MVC so I ditched that effort. It was getting called after the GetCourses method anyway, so it was dead end too.
I'm newer to MVC and come from a WebForms background where I was used to being able to throw this kind of code in my PageInit, but am struggling to figure out how to pull this off in MVC. Can someone help me figure out what I am doing wrong or if I need to go about this a different way?
public ActionResult GetCourses()
{
TempData["EthosURI"] = "redacted";
TempData["Token"] = GetAccessToken().ToString();
IEnumerable<Course> courses = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri((string)TempData["EthosURI"]);
client.DefaultRequestHeaders.Add("Authorization", "Bearer {" + (string)TempData["Token"] + "}");
client.DefaultRequestHeaders.Add("Accept", "application/json");
//HTTP GET
var responseTask = client.GetAsync("courses");
responseTask.Wait();
var result = responseTask.Result;
if (result.IsSuccessStatusCode)
{
var readTask = result.Content.ReadAsAsync<IList<Course>>();
readTask.Wait();
courses = readTask.Result;
}
else //web api sent error response
{
//log response status here..
courses = Enumerable.Empty<Course>();
ModelState.AddModelError(string.Empty, "Server error. Please contact administrator.");
}
}
return View(courses);
}
public static async Task<string> GetAccessToken()
{
var token = await GetAccessTokenAsync("redactedUrl", "redactedAPIKey");
return token;
}
public static async Task<string> GetAccessTokenAsync(string ethosURI, string apiKey)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(ethosURI);
client.DefaultRequestHeaders.Accept.Clear();
HttpRequestMessage request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(ethosURI)
};
request.Headers.Clear();
request.Headers.Add("Authorization", $"Bearer {apiKey}");
request.Headers.Add("Accept", "application/json");
request.Headers.CacheControl = new CacheControlHeaderValue() { NoCache = true };
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
The (non-blocking) way in C# to wait for a task to complete is to use the await keyword. And for a method to use the await keyword, it has to be marked async. By using await, you not only wait for the task to complete, but also the current thread is not blocked. Wrapping an asynchronous operation in another method would not make it synchronous. In other words, the asynchronous nature propagates up the call hierarchy and the caller has to await. So, the GetAccessToken() still has to be awaited. A controller action can be marked asynchronous as well, so you probably want:
public async Task<ActionResult> GetCourses()
{
TempData["EthosURI"] = "redacted";
TempData["Token"] = (await GetAccessToken()).ToString(); // note the additional parentheses
....
Note the additional parantheses above before calling ToString(). However, since GetAccessToken() already returns a string, you don't need the redundant ToString() call:
TempData["Token"] = await GetAccessToken();
Now, you can also change this:
var readTask = result.Content.ReadAsAsync<IList<Course>>();
readTask.Wait();
courses = readTask.Result;
to just:
courses = await result.Content.ReadAsAsync<IList<Course>>();
Microsoft has quite good documentation on asynchronous programming and I would recommend checking it out.
That's not how async works in C#. You need either to make GetCourses() async AND await for GetAccessToken(), or use dirty hack GetAccessToken().GetAwaiter().GetResult() but it may become not safe in certain circumstances.
I previously posted a question about using HTTPClient with async/await. Now I'm trying to figure out how to do this in such a way as to actually make the Post calls execute at the same time while still being able to handle the resulting HttpResponseMessage.
This is what I've come up with. However, being a noob to await/async, I'm still unsure about if I'm doing this correctly or not. Could someone verify that this is the proper way to do this ... or at least a proper way.
public async Task ProcessAsync() {
//Query lists
List<Member> list = dbContext.Users.Where(u => u.IsMember).ToList();
//Add members to mailing list through web service
await AddMembersAsync(list);
}
private async Task AddMembersAsync(List<Member> members) {
using(var _client = new HttpClient()) {
//Initialize Http Client
...
var responses = await Task.WhenAll(members.Select(x => PostMemberAsync(x,_client)));
await Task.WhenAll(responses.Select(r => ProcessResponseAsync(r,client)));
}
}
private async Task<HttpResponseMessage> PostMemberAsync(Member member, HttpClient client) {
var jss = new JavaScriptSerializer();
var content = jss.Serialize(new MemberPost() {
email_address = member.email,
...
});
return await client.PostAsync("uri",new StringContent(content, Encoding.UTF8, "application/json"));
}
private async Task ProcessResponsesAsync(HttpResponseMessage response, HttpClient client) {
if(response.IsSuccessStatusCode) {
var responseText = await response.Content.ReadAsStringAsync();
var jss = new JavaScriptSerializer();
var userid = jss.Deserialize<MemberResponse>(responseText);
//Store mailing user's id
...
}
response.Dispose();
}
This appears to me like it would be correct. However, I have a slight problem with this. I need to tie each HttpResponseMessage to the member for which the message was created. My current code only returns Task but the response message does not contain a link to the user. (The service I'm posting to returns an id specific to the service. I need to keep track of that Id for each user so that I have a link between the member id and the service id).
Does my requirement of linking the id from the response message to the member make it unrealistic to use the above code or is there a way to somehow return the member as part of the task results?
I'm suggesting this without trying it out so please be careful but I would replace these two lines:
var responses = await Task.WhenAll(members.Select(x => PostMemberAsync(x,_client)));
await Task.WhenAll(responses.Select(r => ProcessResponseAsync(r,client)));
with this:
await Task.WhenAll(members.Select(async x =>
{
var response = await PostMemberAsync(x, _client);
await ProcessResponseAsync(response, client, x);
}));
And of course you need to enhance ProcessResponseAsync by the argument Member
I need to tie each HttpResponseMessage to the member for which the message was created.
When doing asynchronous programming, I find it useful to avoid side effects. In other words, if you have a method that calculates or determines something, return that value from the method rather than saving it in some member variable.
private async Task<MemberResponse> ProcessResponseAsync(HttpResponseMessage response, HttpClient client)
{
using (response)
{
if(response.IsSuccessStatusCode)
{
var responseText = await response.Content.ReadAsStringAsync();
var jss = new JavaScriptSerializer();
var userid = jss.Deserialize<MemberResponse>(responseText);
return userid;
}
else
{ ... }
}
}
Add a small helper method, and your calling code becomes quite clean:
private async Task<HttpResponseMessage> ProcessMemberAsync(Member member, HttpClient client)
{
var response = await PostMemberAsync(member, client);
return await ProcessResponseAsync(response, client);
}
private async Task AddMembersAsync(List<Member> members)
{
using(var client = new HttpClient())
{
... // Initialize HttpClient
var responses = await Task.WhenAll(members.Select(x => ProcessMemberAsync(x, client)));
for (int i = 0; i != members.Count; ++i)
{
var member = members[i];
var response = responses[i];
...
}
}
}
Is there anything wrong with my code here? I keep getting this error:
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
My HttpRequestMessage is inside a Func so I figured I get a brand new request each time I pass in func().
public async Task<HttpResponseMessage> GetAsync(HttpRequestMessage request)
{
return await RequestAsync(() => request);
}
public async Task<HttpResponseMessage> RequestAsync(Func<HttpRequestMessage> func)
{
var response = await ProcessRequestAsync(func);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
WaitForSomeTime();
response = await ProcessRequestAsync(func);
}
return response;
}
private async Task<HttpResponseMessage> ProcessRequestAsync(Func<HttpRequestMessage> func)
{
var client = new HttpClient();
var response = await client.SendAsync(func()).ConfigureAwait(false);
return response;
}
You are calling the same func parameter twice:
var response = await ProcessRequestAsync(func);
//...
response = await ProcessRequestAsync(func);
In this case func returns the same request every single time. It doesn't generate a new one every time you call it. If you truly need a different request each time then func needs to return a new message each call:
var response = await GetAsync(() => new HttpRequestMessage()); // Create a real request.
public async Task<HttpResponseMessage> GetAsync(Func<HttpRequestMessage> requestGenerator)
{
return await RequestAsync(() => requestGenerator());
}
I had the same issue, but no repetition in my code. Turned out I had added a watch on an asynchronous process. That watch called the process while I stepped through the code, so that when I got to the line I was trying to debug it crashed with this error message.
Removing all watches solved the problem.
Leaving this here for other people who might have the same problem.