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?
Related
I want to call one 3rd party Rest API from the asp.net core application with the pooling mechanism.
Here in the below sample code, I want to call _client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or my pooling call limit gets exceeded.
public async Task<Result> TrackDocumentProcess(RequestModel model)
{
var response = await _client.DocumentProcess(model);
if(response.IsSuccess)
{
if(response.Value.Status != "Success")
//Call the _client.DocumentProcess(model) API after 10 seconds.
else
return Result.Success();
}
}
public async Task<Result<ResponseModel>> DocumentProcess(RequestModel model)
{
var authToken = GetToken();
using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(new HttpMethod("POST"), $"https://apiurl.com/api/TrackDocumentProcessStatus/{model.DocumentId}");
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {authToken.Result.Value.Token}");
var response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(content))
{
var userRequestResponse = JsonSerializer.Deserialize<ResponseModel>(content);
return Result.Success(userRequestResponse);
}
return Result.Failure<ResponseModel>("Error...");
}
Can you please suggest any best practices to solve this problem?
Thanks.
Here in the below sample code, I want to call
_client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or
my pooling call limit gets exceeded.
Well based on your scenario, we can consider two standard implementaion. First one we can implement using PeriodicTimer which provides a periodic timer that would call as per the given time interval.
Another one, would be using BackgroundService worker process which would continue calling API unless the certain condition are meet.
Using PeriodicTimer:
public async Task<IActionResult> DocumentProcess()
{
var status = await TrackDocumentProcessStatus();
if (status == "Success")
{
return Ok(status);
}
else
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
continue;
}
}
return Ok();
}
Explanation:
As you can see, when DocumentProcess method would invocke, it will call the TrackDocumentProcessStatus method thus, the API and if API return pending it update the status and will call again within next 10 seconds and process would continue until it gets success status.
Method Would Invoked Every 10 Seconds:
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 3;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Output:
Note:
If you would like to know more details on it you could check our official document here
BackgroundService worker process:
It would be a individual background worker service which would continue running and check your status behind. You can achieve your requirement as following:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
var status = await TrackDocumentProcessStatus();
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
Console.WriteLine(status);
await Task.Delay(1000, stoppingToken);
}
}
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 1;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Note:
Both implementaion would call your API after a certain interval for checking status from another API. The difference of these implementation is background worker service would continue in background, no other action wwould required just like widows service, If you would like to know more details on background worker service you could check our official document here
Here is case:
I have wpf application on about 80 clients and they communicate single .net framework api for data process. I have stopwatch tracker some places for tracking durations on Wpf and api apps. Code samples :
My api attribute :
public class DurationControlLoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = actionContext.ActionDescriptor.ActionName;
actionContext.ActionArguments.Add("_stopwatch_", Stopwatch.StartNew());
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (ConfigurationManager.AppSettings["ApiLogger"].ToLower().Trim() != "true")
return;
var controller = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
var stopWatch = (Stopwatch)actionExecutedContext.ActionContext.ActionArguments["_stopwatch_"];
stopWatch.Stop();
var scope = actionExecutedContext.ActionContext.Request.GetDependencyScope();
var commonService = (ICommonService)scope.GetService(typeof(ICommonService));
commonService.InsertLog(new Model.Common.LogModel
{
InsertDate = DateTime.Now.ToString(),
LogLevel = "API",
MachineName = "API",
Message = $"Controller : {controller} - Action : {action} - TotalSeconds : {stopWatch.Elapsed.TotalSeconds}",
StackTrace = string.Empty
});
}
}
Action sample :
[HttpPost]
[DurationControlLogger]
public bool InsertProduct(ProductModel model)
{
return _mainService.TracingService.InsertProduct(model);
}
This action process duration is about 0,03 seconds. On the other hand, wpf api call duration is about 10 seconds. Wpf code blocks are down below:
var Stopwatch = Stopwatch.StartNew();
var isSuccess = DataHelper.InsertProduct(Product);
Stopwatch.Stop();
if (Stopwatch.Elapsed.TotalSeconds > 2)
DataHelper.InsertTraceLog($"ProducrtBusiness - InsertProduct TotalSecond : {Stopwatch.Elapsed.TotalSeconds}");
DataHelper.InsertProduct method does basic http post request. Code is here :
public static class HttpClientHelper
{
public static T Post<T>(object model, string url)
{
var resultStatus = false;
T resultData = default(T);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(url, content).Result;
if (response.IsSuccessStatusCode)
{
string data = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<T>(data);
resultStatus = response.StatusCode == System.Net.HttpStatusCode.OK;
resultData = result;
}
}
return resultStatus ? resultData : default(T);
}
.....
Does anyone has any idea about that situation?
EDIT
I add one last log code. Here is code:
`
var stopwatch = Stopwatch.StartNew();
HttpResponseMessage response = client.PostAsync(url, content).Result;
stopwatch.Stop();
if (stopwatch.Elapsed.TotalSeconds > 2)
{
AppendToFile($"{DateTime.Now.ToString()} - {model.ToString()} - {stopwatch.Elapsed.TotalSeconds} - Content: {jsonData}");
}
`
This log duration still sometimes is 8-10 sec.
Well, it is likely (from what Ive seen) that you are running this code on the main message pump, (UI thread). During this method call you are performing an asynchronous call (PostAsync) and calling the 'Result' - making it run synchronously. However, while your UI thread was waiting, some other messages (UI updates?) where queued on the message pump, and were processed before your call to end the stopwatch (higher DispatcherPriority?) - delaying the timer end.
This is pure speculation, but I would recommend that you use some async/await in your asynchronous code, and try and perform these data access tasks on a separate thread
I changed my HttpClientHelper class post method to this :
public static async Task<T> PostAsync<T>(object model, string url)
{
var resultStatus = false;
T resultData = default(T);
using (var client = new HttpClient(
new HttpClientHandler()
{
UseProxy = false
}
))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonData = JsonConvert.SerializeObject(model);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
var stopwatch = Stopwatch.StartNew();
HttpResponseMessage response = await client.PostAsync(url, content);
stopwatch.Stop();
if (stopwatch.Elapsed.TotalSeconds > 2)
{
AppendToFile($"{DateTime.Now.ToString()} - {model.ToString()} - {stopwatch.Elapsed.TotalSeconds} - Content: {jsonData}");
}
if (response.IsSuccessStatusCode)
{
string data = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(data);
resultStatus = response.StatusCode == System.Net.HttpStatusCode.OK;
resultData = result;
}
}
return resultStatus ? resultData : default(T);
}
client.PostAsync() method has to run async for my issue. I think that related some thread managment issues. But this code works for me now.
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.
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;
}
}
}
I have a question regarding strange behavior of a process that performs lots of async unsuccessful requests to a server.
The strange thing is that it's almost impossible to exit/stop/kill the process.
If you compile and run this program on a windows 8 machine the process hangs and I haven't found a solution how to kill it. Only reboot helps.
Could somebody explain the mentioned behavior please.
Thanks.
PS: This is what I get when use taskmanager to kill the process
internal class Program
{
private const int totalRequests = 2000;
private static void Main(string[] args)
{
var cancellationTockenSource = new CancellationTokenSource();
LoadTest(cancellationTockenSource);
Console.WriteLine("Press any key to break");
Console.ReadLine();
cancellationTockenSource.Cancel();
}
private static async Task LoadTest(CancellationTokenSource cancellationTockenSource)
{
Stopwatch stopWatch = Stopwatch.StartNew();
var requests = new List<Task>(totalRequests);
for (int i = 0; i < totalRequests; ++i)
{
requests.Add(Request(cancellationTockenSource.Token));
}
try
{
await Task.WhenAll(requests);
}
catch (Exception e)
{
Console.WriteLine(e);
}
stopWatch.Stop();
Console.WriteLine("{0} req/sec", stopWatch.Elapsed.TotalSeconds/totalRequests);
}
private static HttpRequestMessage CreateMessage()
{
var url = new Uri("http://ya.ru:8080/234234234234x");
var message = new HttpRequestMessage(HttpMethod.Get, url);
message.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
return message;
}
protected static async Task<string> Request(CancellationToken token)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.SendAsync(CreateMessage(), token);
string content = await response.Content.ReadAsStringAsync();
return content;
}
}
}