I need some help figuring out why the following code in the continueWith block is not being executed for a long running service call.
public static async void postServiceAsync(string json, string postServiceUrl, string callbackUrl, string clientId,
string tenant, string secret, string d365Environment, TraceWriter log)
{
HttpClient client = new HttpClient();
//Get authorization header
string authHeader = await D365Authorization.getAccessToken(clientId, tenant, secret, d365Environment);
client.DefaultRequestHeaders.Add("Authorization", authHeader);
var httpContent = new StringContent(json);
client.Timeout = TimeSpan.FromMinutes(90);
client.PostAsync(postServiceUrl, httpContent).ContinueWith(async (result) =>
{
//call callback URL
//This is not executed after a long running service that runs for 20 minutes.
}
}
The continueWith code does get run if the service execution time is short though. I thought it was a timeout issue so I added the client.Timeout value. I tried calling the service in Postman and a value is returned even after waiting for 20+ minutes. I am not using await as I want the execution to continue after calling PostAsync. I just want the continueWith callback executed after the long running service execution has completed. Thanks for your help!
The above method called postServiceAsync is called from an Azure function which is being called from an Azure Logic App http webhook action. Here is the Azure function:
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
...
PostServiceAsync.postServiceAsync(json, shipServiceUrl, callbackUrl, clientId, tenant, secret, d365Environment, log);
var resp = req.CreateResponse(HttpStatusCode.Accepted);
return resp;
}
}
From the Azure function, I need to return the Accepted status code right away. After I've finished calling the long running service using PostAsync, I need to post to the callback URL, which is what I am doing in the continueWith block. Like I mentioned, it works if the service runtime is short. I tried Camilo's suggestion of adding await but the continueWith code did not get executed. I also tried getting rid of the continueWith and just added the code after "await client.PostAsync(...)".
It turns out that there is an Azure function 230 second timeout for http calls without a response. I might not be able to use an Azure function for my purposes.
Related
I have a web app that connects to an external API.
That API has a limit of 3 connections per second.
I have a method that gets employee data for a whole factory.
It works fine, but I've found that if a particular factory has a lot of employees, I hit the API connection limit and get an error.
(429) API calls exceeded...maximum 3 per Second
So I decided to use await Task.Delay(1000) to set a 1 second delay, every time this method is used.
Now it seems to have reduced the number of errors I get, but I am still getting a few limit errors.
Is there another method I could use to ensure my limit is not reached?
Here is my code:
public async Task<YourSessionResponder> GetAll(Guid factoryId)
{
UserSession.AuthData sessionManager = new UserSession.AuthData
{
UserName = "xxxx",
Password = "xxxx"
};
ISessionHandler sessionMgr = new APIclient();
YourSessionResponder response;
response = await sessionMgr.GetDataAsync(sessionManager, new ListerRequest
{
FactoryId = factoryId;
await Task.Delay(1000);
return response;
}
I call it like this:
var yourEmployees = GetAll(factoryId);
I have a web app that connects to an external API.
Your current code limits the number of outgoing requests made by a single incoming request to your API. What you need to do is limit all of your outgoing requests, app-wide.
It's possible to do this using a SemaphoreSlim:
private static readonly SemaphoreSlim Mutex = new(1);
public async Task<YourSessionResponder> GetAll(Guid factoryId)
{
...
YourSessionResponder response;
await Mutex.WaitAsync();
try
{
response = await sessionMgr.GetDataAsync(...);
await Task.Delay(1000);
}
finally
{
Mutex.Release();
}
return response;
}
But I would take a different approach...
Is there another method I could use to ensure my limit is not reached?
Generally, I recommend just retrying on 429 errors, using de-correlated jittered exponential backoff (see Polly for an easy implementation). That way, when you're "under budget" for the time period, your requests go through immediately, and they only slow down when you hit your API limit.
From a comment on the question:
I am calling it like this: var yourEmployees = GetAll(factoryId);
Then you're not awaiting the task. While there's a 1-second delay after each network operation, you're still firing off all of the network operations in rapid succession. You need to await the task before moving on to the next one:
var yourEmployees = await GetAll(factoryId);
Assuming that this is happening in some kind of loop or repeated operation, of course. Otherwise, where would all of these different network tasks be coming from? Whatever high-level logic is invoking the multiple network operations, that logic needs to await one before moving on to the next.
I have an middleware which executes based on specific routing and redirects the request to call another service and the response of the service call need to be sent back to the caller.
public Task Invoke(HttpContext context)
{
return GetServiceDataAsync(context);
}
private async Task<object> GetServiceDataAsync(HttpContext context)
{
var response = service.GetDataAsync();
return await response;
}
When executing this call, I do see the service call returns response properly. But, the response is not written back as HttpResponseMessage so the caller can read again from the body.
In this case, the response was already read and converted to response. Need some pointers on how to provide the same response back to the caller. With this code, I'm seeing 200 status code, but the body is empty. TIA !
I’m creating an API that serves as the bridge between the app and 2 other APIs. I want to know if what is the best way to do this. I’m using HttpClient. The app has almost a thousand users so if I use synchronous calls does that mean that if a user calls the API, then the other users have to wait until the 1st user gets the response before it proceeds to the other API requests? Is there a better way of doing an API like this?
Here is a sample of my code using synchronous:
[HttpGet]
[Route("api/apiname")]
public String GetNumberofP([FromUri]GetNumberofPRequest getNPRequest){
var request = JsonConvert.SerializeObject(getNPRequest);
string errorMessage = "";
try{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.gettoken());
var response = httpClient.GetAsync("api/MobileApp/GetNumberP?"
+ "strCardNumber=" + getNPRequest.strCardNumber
+ "&strDateOfBirth=" + getNPRequest.strDateOfBirth).Result;
return response;
}
catch (Exception e){
throw utils.ReturnException("GetNumberofP", e, errorMessage);
}
}
if I use synchronous calls does that mean that if a user calls the API, then the other users have to wait until the 1st user gets the response before it proceeds to the other API requests
No. When a request comes into the pipeline, a new thread is spawned by the framework. So if 1,000 requests come in at the same time, the 1,000th user will not have to wait for the other 999 requests to finish.
You are better off using async code for this anyway. For any I/O like network requests, you're usually better off for performance letting a background thread do the waiting. Side note, you never want to call .Result because that forces the async code to become blocking and effectively becomes synchronous.
t's always easy to turn a synchronous call into an asynchronous one, but the other way around is fraught with danger. You should make your API asynchronous.
[HttpGet]
[Route("api/apiname")]
public Task<string> GetNumberofP([FromUri]GetNumberofPRequest getNPRequest)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.gettoken());
return httpClient.GetAsync($"api/MobileApp/GetNumberP?strCardNumber={getNPRequest.strCardNumber}&strDateOfBirth={getNPRequest.strDateOfBirth}");
}
You should also consider creating a new httpClient for each call.
It seems you're missing the async and await keywords.
public async String GetNumberofP([FromUri]GetNumberofPRequest getNPRequest){
(...)
var response = await httpClient.GetAsync();
I am developing a WPF application where I am consuming some HTTP API to get data, I am using System.Net.Http.HttpClient class to make API calls, but as soon I call GetAsync method of the client I don't get any response back from the API and the debugger just goes away.
Below is the line I am using to make the call where the debugger gets lost
var response = await client.GetAsync(endpoint);
I don't know why I am not getting any response from the API.
Note: The API is working on browser as it is a simple GET call.
Try following
var response = await client.GetAsync(endpoint).ConfigureAwait(false);
Making the HTTP call inside Task.Run worked for me, I assume that this has something to relate with UI Thread blocking.
Thanks everyone for the help!!
var task = Task.Run(() => {
var response = await client.GetAsync(endpoint);
});
await task;
I'm trying to use a service bus Azure function, where I accept a BrokeredMessage, then perform http requests, and then afterward decide whether to complete, abandon, or dead letter the message. But I've been finding the BrokeredMessage is being disposed early if I await an http request. It's throwing System.ObjectDisposedException: 'BrokeredMessage has been disposed.' if I try to use it at all.
Example:
public static async void Run(BrokeredMessage message, TraceWriter log)
{
var httpClient = new HttpClient()
{
BaseAddress = new Uri("http://google.com")
};
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var request = new HttpRequestMessage(HttpMethod.Get, "/");
HttpResponseMessage response = await httpClient.SendAsync(request);
message.DeadLetter(); //Throws exception
}
I'm not sure if I'm doing something wrong or missing something but I can't figure out how to make any actions I need to do with the message after the await work correctly.
You shouldn't call Complete() explicitly. Azure Functions runtime will complete the message if the function finishes successfully or abandon if the function fails.
The Functions runtime receives a message in PeekLock mode and calls Complete on the message if the function finishes successfully, or calls Abandon if the function fails.
from docs
The issue was the function needs to return Task instead of void for the async to work correctly. After making that change it awaits correctly.