GetStringAsync timeout has no effect - c#

I have the following code:
public static string createRequest(string url, int timeout = 1)
{
Task<string> responseString;
using (var client = new System.Net.Http.HttpClient())
{
responseString = client.GetStringAsync(url);
responseString.Wait(new TimeSpan(0, 0, timeout));
}
}
If I run this code the first time in the debugger, the timeout will occure only after a long while (1-2 minutes). The second time it's running way faster and finishes after round about 3-4 seconds.
If i put a break point on some code after this invokation, it's sometimes running faster but mainly it takes a long while.
Why is the code taking such a long timespan even though there's a defined timeout?
In fact responseString.Status is TaskStatus.Canceled which is exactly what I expect (no device bound to this IP).
What is wrong with this code? Thank you :)

You should not set the timeout of the Task<string> object. You have to set HttpClient.Timeout. Also, please consider using an async/await approach:
public static async Task<string> createRequest(string url, int timeout = 1)
{
using(var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(timeout);
string response = await client.GetStringAsync(url);
// Handle response here
return handledResponse; // You can return a raw string
}
}

Probably not the most beautyful version but exactly doing what I expected:
public string AsyncRequest(string url, int timeout)
{
string retval = null;
using (var client = new System.Net.Http.HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(timeout);
try
{
retval = client.GetStringAsync(url).Result;
return retval;
}
catch
{
AllnetALL3073RemoteSwitch_found = false;
return null;
}
}
}

Related

HttpClient taking too long

I created a .Net Framework 4.7.2 console app that concurrently makes many requests in an API hosted in AWS. My problem is that the requests are taking too long.
The API's response time is usually 100ms-400ms according to the taget group monitoring in AWS console but in my application the time elapsed of each request starts at 1 second and keeps increasing until 11 seconds.
I'm already aware that HttpClient doesn't close connections properly so we shouldn't use using and instead always use one instance for each application.
I already found a similar question but the answer didn't solve it.
When I set MaxDegreeOfParallelism to 1, the response time in the application is similar to the app. This seem to be problem that occurs in HttpClient in a multi thread.
This is how i'm doing the requests :
public static class RequestMaker
{
private static readonly string _urlHttp = "http://apidomain.com/api/apiname";
private static readonly HttpClient _httpClient = new HttpClient();
public static async Task<string> PostAsync(string postData)
{
bool IsSuccessStatusCode = false;
int maxRetries = 5;
int count = 0;
do
{
try
{
Stopwatch watcher = Stopwatch.StartNew();
using (HttpContent content = new StringContent(postData, Encoding.UTF8, "application/json"))
using (HttpResponseMessage result = await _httpClient.PostAsync(_urlHttp, content).ConfigureAwait(false))
{
watcher.Stop();
Console.WriteLine("Elapsed = " + watcher.ElapsedMilliseconds.ToString("N0"));
IsSuccessStatusCode = result.IsSuccessStatusCode;
if (IsSuccessStatusCode)
return await result.Content.ReadAsStringAsync().ConfigureAwait(false);
count++;
if (count > maxRetries)
return "";
Console.WriteLine($"Retrying request because of request status code {result.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
count++;
if (count > maxRetries)
return "";
}
} while (!IsSuccessStatusCode);
return "";
}
}
This is my function calling the request concurrently :
static void RunBatchMany(List<string> list)
{
var getCustomerBlock = new TransformBlock<string, long>(
async lstRec =>
{
ApiInputObject apiInput = new ApiInputObject();
// PrepareInputObject
string postData = JsonConvert.SerializeObject(apiInput);
Stopwatch watcher = Stopwatch.StartNew();
string json = await RequestMaker.PostAsync(postData);
ApiResponseObject res = JsonConvert.DeserializeObject<ApiResponseObject>(json);
watcher.Stop();
return watcher.ElapsedMilliseconds;
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 8
});
foreach (var id in list)
getCustomerBlock.Post(id);
getCustomerBlock.Complete();
getCustomerBlock.Completion.Wait();
}
Try to isolate the await logic and make only one call to the URL without using a loop :
HttpResponseMessage result = await _httpClient.PostAsync(_urlHttp, content).ConfigureAwait(false)
Make a Unit Test out of that call. Call it once. Is the HttpResponse received in the suggested time? If Yes: Then slowly add the loop and the other logic things around. You use a lot of async programming, but why? For what exactly dou you need it? Why do you use a "static" Request Maker?

Why RestAPI occasionally returns 429 TooManyRequest response?

I send a request to the API and sometimes receive the response with an HTTP 429 status code (TooManyRequests).
On average, for 10 requests, 2 will return 429 response and the remaining 8 return the correct value.
It also happened to me that when it was the first request (so there is no option for TooManyRequests)
public static List<ResponseObject> GetProductsFromRestAPI(int[] ProductIdArray )
{
List<ResponseObject> products = new List<ResponseObject>();
string action;
for (int i = 0; i < ProductIdArray.Length; i++)
{
action = "products/" + ProductIdArray[i].ToString();
client = AddHeadersToClient(action, new RestClient("https://api.usedbythiscode.com/")); //required by this API.
var currentProduct = RequestsLib.GetProduct(client, action);
products.Add(currentProduct);
}
return products;
}
public static Product GetProduct(RestClient restClient, string action) //todo test this for bugs
{
var result = new Product();
var request = new RestRequest(action, Method.GET);
var response = SendRequest(restClient, request);//Here I sometimes get response with 429.
//.. Other code
return result;
}
public static async Task<IRestResponse> SendRequest(RestClient restClient, RestRequest request)
{
return await restClient.ExecuteGetAsync(request);
}
Temporarily resolved it by sending another request with do while loop and usually second request return right answer.
do
{
SendRequest(restClient, request);
}
while (StatusCode != 200);
Where could the cause of the error lie?
Is it possible that I have unclosed requests?
Is creating multiple RestSharp clients a good practice?
EDIT:
The problem was on the server side. All I had to do was report the bug to the admins who provided the API. Thank you for help.
429 is Too Many Requests. Most APIs have some kind of rate-limiting in place so that a single client can't take down their server with too many requests.
The proper response for 429 is to retry. I recommend using Polly for retry logic, but be aware that HandleTransientHttpError doesn't consider 429 a transient error.
I agree with #mason, you should use async method with Task<> and await response Here is the part of login side of my mobileApp-project in Xamarin. You may want to see how to use async with Task<> easily.
public async Task<BSUser> ValidateUser(string userName, string password)
{
string url = Xamarin.Essentials.Preferences.Get(Constants.URL_KEY, "") + "/api/Validateuser";
HttpClient _Client = new HttpClient();
var data = new Dictionary<string, string>
{
{"userName", userName},
{"password", password}
};
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage httpResponse = await _Client.PostAsync(url, content);
if (httpResponse.IsSuccessStatusCode)
{
try {
var responseData = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject(responseData).ToString();
UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(result);
BSUser value = new BSUser();
value.UserName = userInfo.userCode;
return value;
}
catch (Java.Net.SocketException e)
{
Console.WriteLine("Hata", e);
return null;
}
}
else
{
return null;
}
}
catch (SystemException)
{
return null;
}
}

How to use HttpClient.PostAsync asynchronous when the URL responds after an artificial timeout?

How can HttpClient.PostAsync be used to post HTTP requests to URLs which have an artificial time for sending back a response.
Please note the URL and the parameter sleep=30 which is used to introduce an artificial delay of 30 seconds before sending back an HTTP response.
Console.WriteLine("Start time : " + DateTime.Now);
for (int i = 1; i <= 10; i++)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(#"http://fake-response.appspot.com/api/?data={Hello World}&sleep=30");
//client.BaseAddress = new Uri(#"http://fake-response.appspot.com/api/?data={Hello World}&status=200");
client.Timeout = new TimeSpan(0, 0, 60);
var parameters = new Dictionary<string, string>();
parameters["ContentType"] = "text/plain;charset=UTF-8";
//Create Task to POST to the URL
client.DefaultRequestHeaders.ExpectContinue = true;
var response = await client.PostAsync(client.BaseAddress, new FormUrlEncodedContent(parameters));
Task<bool> b1 = ProcessURLAsync(response, 1, 5, 2);
}
}
Console.WriteLine("End time : " + DateTime.Now);
What needs to be done is that async HTTP Posts need to be made in a loop and should not be dependent on the timeout specified in the URL.
However, the PostAsync times out before a response is received.
Please check the time needed for POSTing the 2 different URLs in a loop of 10 async POSTs
I checked HttpClient.DefaultRequestHeaders.ExpectContinue , but I do not think this might help in this use case.
That artificial delay is no different from network timeout, from client's perspective. So you should set client.Timeout to the maximum expected artificial delay + real network timeout time. If you don't want to block waiting for response - just not await Task returned from PostAsync. You can store all such tasks in some list and wait them all to complete with await Task.WhenAll(yourTaskList). Or you can use ContinueWith to perform specific actions when given task will be completed. However, if you care about response at all - you have to set large enough timeout anyway, otherwise request will be aborted prematurely.
Here is some sample code to help you out
static async void MakeRequests()
{
var requests = new List<Task<bool>>();
for (int i = 1; i <= 10; i++)
{
// no await here, so, not await MakeRequest(i);
requests.Add(MakeRequest(i));
}
// now all 10 requests are running in parallel
try {
await Task.WhenAll(requests);
}
catch {
// no need to handle it here - we handle all errors below
}
// if we are here, all requests are either completed or failed, inspect their results
foreach (var request in requests) {
if (request.IsCanceled) {
// failed by timeout
}
else if (request.IsFaulted) {
// failed
Log(request.Exception);
}
else {
// success
bool result = request.Result;
// handle your result here if needed
}
}
}
static async Task<bool> MakeRequest(int i) {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(#"http://fake-response.appspot.com/api/?data={Hello World}&sleep=30");
//client.BaseAddress = new Uri(#"http://fake-response.appspot.com/api/?data={Hello World}&status=200");
// no timeout here, or set to max expected delay
//client.Timeout = new TimeSpan(0, 0, 60);
var parameters = new Dictionary<string, string>();
parameters["ContentType"] = "text/plain;charset=UTF-8";
//Create Task to POST to the URL
client.DefaultRequestHeaders.ExpectContinue = true;
var response = await client.PostAsync(client.BaseAddress, new FormUrlEncodedContent(parameters));
Task<bool> b1 = ProcessURLAsync(response, 1, 5, 2);
return b1;
}
}
However, the PostAsync times out before a response is received.
This method times out because you set HttpClient.Timeout property to 10 seconds. Setting this property instructs the client to time out after a specified time if response is not received.

Endless wait for async Web Response

i have the following problem, i try to wait for for an Async Web Response.
But it never finished.
public string getTermine(string trmId)
{
System.Threading.Tasks.Task<string> lisi = LoadTermine((HttpWebRequest)WebRequest.Create("http://" + curent.usrCH + apiKey + curent.phrase + apiTrmIDIS + trmId));//Request get String result like http://ajax.googleapis.com/ajax/services/search/web?v=1.0&start="+i+"&q=
lisi.Wait();
return lisi.Result;
}
private async System.Threading.Tasks.Taskstring>LoadTermine(HttpWebRequest myRequest)
{
//List<Termine> terminListe = new List<Termine>();
List<Appointment> Resu = null;
using (WebResponse response = await myRequest.GetResponseAsync())
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(response.GetResponseStream()))
{
Resu = reader.ReadToEnd();
}
}
return Resu;
}
P.S. I cant use and synchronous request because this methods are an part of the Base code which is used by iOS, WinPhone and Android and i dont know why i cant get an synchronous WebResponse.
You are creating a deadlock by calling .Result on the task.
You could do something like this where the remoteUrl variabled is the url of your web service
private async System.Threading.Tasks.Task<string> LoadTermineAsync(HttpWebRequest myRequest)
{
using (var client = new HttpClient()) {
using (var request = new HttpRequestMessage(HttpMethod.Get, myRemoteUrl)) {
var response = await client.SendAsync(request).ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
}
}
For more info on Async/Await
And this evolve video is a little bit more advanced.

How to await for multiple tasks when using lambda exp on the event handler

I have 2 classes Main.cs and Processing.cs (P and M for short) class M makes a call to P passing an html link, P in tern downloads, converts to Base64, renames and saves the file then returns a string back to M, now i need M to wait until all of this is complete to proceed but i have not been able to.
I used a lambda expression with the event handler to be able to do everything in that function as opposed to a separate function for the event trigger so i would be able to return the string with the Base64 converted file, but it just returns the empty string, not wait until it has been assigned.
I thought the taskA.Wait() call would make it wait until all processing was done, but it's not the case
If anyone has any ideas I would appreciate the help.
the call from Main.cs is like this:
Processing processing = new processing();
String _link = “http://www.something.com”;
var ResultBase64_Val = processing.FileToBase64(_link).Result;
In Processing.cs the function is:
public async Task<String> FileToBase64(String filePath)
{
String convertedFile = "";
WebClient client = new WebClient();
Task taskA = Task.Factory.StartNew(() => client.OpenReadCompleted += async (object sender, OpenReadCompletedEventArgs e) =>
{
byte[] buffer = new byte[e.Result.Length];
buffer = new byte[e.Result.Length];
await e.Result.ReadAsync(buffer, 0, buffer.Length);
convertedFile = Convert.ToBase64String(buffer);
});
client.OpenReadAsync(new Uri(filePath));
taskA.Wait();
return convertedFile;
}
Thanks,
Bob
The problem with your code is that the task started with Task.Factory.StartNew completes instantly, well before OpenReadCompleted is fired sometime in the future. That said, wrapping a naturally asynchronous API like OpenReadAsync with Task.Run or Task.Factory.StartNew is a bad idea anyway. Even if you waited for the event somehow, or used synchronous OpenRead, you'd be wasting a pool thread.
There's new WebClient.OpenReadTaskAsync method for that:
public async Task<String> FileToBase64(String filePath)
{
using (var client = new WebClient())
using (var stream = await client.OpenReadTaskAsync(new Uri(filePath)))
{
// use stream.ReadAsync
}
}
I also recommend HttpClient over WebClient, the former supports multiple HTTP requests in parallel:
using System.Net.Http;
// ...
public async Task<String> FileToBase64(String filePath)
{
using (var client = new HttpClient())
using (var response = await client.GetAsync(filePath))
using (var stream = await response.Content.ReadAsStreamAsync())
{
return string.Empty;
// use stream.ReadAsync
}
}
If you want to use the WebClient, and you want to return result from the method, you'll want to use the TaskCompletionSource and subscribe to the event normally
public Task<String> FileToBase64(String filePath)
{
TaskCompletionSource<string> completion = new TaskCompletionSource<string>();
String convertedFile = "";
WebClient client = new WebClient();
client.OpenReadCompleted += (s, e) =>
{
TextReader reader = new StreamReader(e.Result);
string result = reader.ReadToEnd();
completion.SetResult(result);
};
client.OpenReadAsync(new Uri(filePath));
return completion.Task;
}
Given this answer, I would recommend using the HttpClient instead of the WebClient
Both Noseratio answer and Shawn Kendrot answer should work. But I will try to use other approach - by WebRequest.
First to do this I will have to extend my WebRequest method by GetRequestStreamAsync() - WP lacks this method:
public static class Extensions
{
public static Task<Stream> GetRequestStreamAsync(this WebRequest webRequest)
{
TaskCompletionSource<Stream> taskComplete = new TaskCompletionSource<Stream>();
webRequest.BeginGetRequestStream(arg =>
{
try
{
Stream requestStream = webRequest.EndGetRequestStream(arg);
taskComplete.TrySetResult(requestStream);
}
catch (Exception ex) { taskComplete.SetException(ex); }
}, webRequest);
return taskComplete.Task;
}
}
Then I would convert your Task to something like this:
public async Task<string> FileToBase64(string filePath)
{
try
{
WebRequest request = WebRequest.Create(new Uri(filePath));
if (request != null)
{
using (Stream resopnse = await request.GetRequestStreamAsync())
using (MemoryStream temp = new MemoryStream())
{
const int BUFFER_SIZE = 1024;
byte[] buf = new byte[BUFFER_SIZE];
int bytesread = 0;
while ((bytesread = await resopnse.ReadAsync(buf, 0, BUFFER_SIZE)) > 0)
temp.Write(buf, 0, bytesread);
return Convert.ToBase64String(temp.ToArray());
}
}
return String.Empty;
}
catch { return String.Empty; }
}
Maybe this will help.
First of all, don't use Task.Factory.StartNew with async methods. Stephen Toub and Stephen Clearly have explained why.
Second, if you're using async-await, then you can use the ~TaskAsync methods of the WebClient class with the Microsoft Async NuGet package - given that you said it's for Windows Phone 8.
On a side note, you should always suffix your asynchronous methods with Async (or TaskAsync if not possible).
Given that, your code becomes:
public async Task<String> FileToBase64Async(String filePath)
{
String convertedFile = "";
var client = new WebClient();
Task taskA = Task.Factory.StartNew(() => client.OpenReadCompleted += async (object sender, OpenReadCompletedEventArgs e) =>
{
var buffer = await webclient.DownloadDataTaskAsync(filePath);
convertedFile = Convert.ToBase64String(buffer);
return convertedFile;
}
}
But you would be better served with the new HttpClient class that you can find on the Microsoft HTTP Client Libraries NuGet package.
And finally, you should not block on async code in the UI thread.

Categories

Resources