I am trying to make a call to a service, the service is supposed to return all the records but i am getting a TaskCancelledException with a message A Task was cancelled and then the results are null. I know there is lots of data (tried with small data and works fine). How can I make it so it continues working (keep grabbing records) without throwing the "A Task was cancelled". Here is my method
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
I see there is a cancellation token being passed, is there a way to handle the cancellation?. Also, this method is being called from here:
public async void GetJsonData()
{
string json = await ObtainData();
}
I would like it to complete getting the request without cancelling.
It sounds from your question and comments that your request is timing out. Try setting the HttpClient.Timeout property
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
httpClient.TimeOut = TimeSpan.MaxValue // change this as you like
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
Edit
Regarding you comment about the exception, this is from MSDN:
The Timeout property must be set before the GetRequestStream or GetResponse method is called.
Create a new instance of HttpClient before making the request, or set it inside the code where you initialize the client (constructor i assume).
As Yuval Itzchakov mentioned you should set the Timeout property to a larger value (but it is a TimeSpan not an integer) and you should use the cancellation token in the HTTP request call if you are using with the async pattern.
And there is no need to use a global HttpClient (as Yuval pointed out) esspecially in this heavy async method calling nightmare (additionally i don't see the reason why should call anything inside the ObtainData async).
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
try
{
using (var httpClient = new HttpClient
{
Timeout = TimeSpan.MaxValue
})
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
using (var response = await httpClient.SendAsync(request, ct))
{
return new ActionResult<string>()
{
Result = await HandleReceipt(response, type)
};
}
}
}
}
catch (Exception ex)
{
return new ActionResult<string>
{
Message = "Problem obtaining data. " + ex
};
}
}
Related
I have an API in .net 5 that runs locally. One of the endpoints is https://localhost:44362/User
Now, I created a console app that will consume the API and defined a method to call the get User.
public async Task<bool> CallAPI()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44362/");
using (HttpResponseMessage response = await client.GetAsync("User"))
{
var resContent = response.Content.ReadAsStringAsync().Result;
return true;
}
}
}
Here is the code in the API
[HttpGet]
public IEnumerable<User> Get()
{
using (var context = new ohabartContext())
{
return context.Users.ToList();
}
}
But when the code using (HttpResponseMessage response = await client.GetAsync("User")) is executed nothing happens. I don't receive any error or whatsoever. I search the net and all other codes look the same in consuming or calling an API.
What seems to be the problem?
ReadAsStringAsync() Serialize the HTTP content to a string as an asynchronous operation. This operation will not block. The returned Task object will complete after all of the content has been written as a string.
HttpResponseMessage response= await client.GetAsync(u);
var resContent = await response.Content.ReadAsStringAsync().Result;
Once the operation completes, the Result property on the returned task object contains the string with the HTTP content.
public async Task<bool> CallAPI()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44362/");
HttpResponseMessage response = await client.GetAsync(u);
var resContent = awit response.Content.ReadAsStringAsync().Result;
return true;
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} " , e.Message);
return false;
}
}
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.getasync?view=net-6.0
I've a .NET Core web app that, once a web method (i.e. Test()) is invoked, call another remote api.
Basically, I do it this way (here's a POST example, called within a web method Test()):
public T PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
T returnValue = default(T);
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
HttpResponseMessage res = null;
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var task = Task.Run(() => client.PostAsync($"{baseAddress}/{url}", body));
task.Wait();
res = task.Result;
succeded = res.IsSuccessStatusCode;
if (succeded)
{
returnValue = JsonConvert.DeserializeObject<T>(res.Content.ReadAsStringAsync().Result);
}
else
{
Log($"PostRead failed, error: {JsonConvert.SerializeObject(res)}");
}
}
}
catch (Exception ex)
{
Log($"PostRead error: {baseAddress}/{url} entity: {JsonConvert.SerializeObject(entity)}");
}
return returnValue;
}
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
Because if they will be called in serial, the last one will take the time of the previous ones, and that's not what I want.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
They will be called in parallel. Nothing in the code would make it in a blocking way.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
There are at least 2 things that need to be fixed here:
Properly using async/await
Properly using HttpClient
The first one is easy:
public async Task<T> PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var responseMessage = await client.PostAsync($"{baseAddress}/{url}", body);
var responseContent = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
succeeded = true;
return JsonConvert.DeserializeObject<T>();
}
else
{
// log...
}
}
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
The second one is a bit more tricky and can quite possibly be the cause of the problems you're seeing. Generally, unless you have some very specific scenario, new HttpClient() is plain wrong. It wastes resources, hides dependencies and prevents mocking. The correct way to do this is by using IHttpClientFactory, which can be very easily abstracted by using the AddHttpClient extension methods when registering the service.
Basically, this would look like this:
services.AddHttpClient<YourClassName>(x =>
{
x.BaseAddress = new Uri(baseAddress);
x.DefaultRequestHeaders.Add("Authorization", MyApiKey);
}
Then it's a matter of getting rid of all using HttpClient in the class and just:
private readonly HttpClient _client;
public MyClassName(HttpClient client) { _client = client; }
public async Task<T> PostRead<T>(string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
string json = JsonConvert.SerializeObject(entity);
var responseMessage = await _client.PostAsync($"{baseAddress}/{url}", body);
//...
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
[HttpClient call using parallel and solve JSON Parse error]
You can use task. when for parallel call . but when you use N number of calls and try to parse as JSON this may throw an error because result concatenation change the Json format so we can use Jarray. merge for combining the result .
Code
public async Task < string > (string baseAddress, string url, out bool succeded, object entity = null) {
using(HttpClient client = new HttpClient()) {
string responseContent = string.Empty;
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var tasks = new List < Task < string >> ();
var jArrayResponse = new JArray();
int count = 10; // number of call required
for (int i = 0; i <= count; i++) {
async Task < string > RemoteCall() {
var response = await client.PostAsync($"{baseAddress}/{url}", body);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(RemoteCall());
}
await Task.WhenAll(tasks);
foreach(var task in tasks) {
string postResponse = await task;
if (postResponse != "[]") {
var userJArray = JArray.Parse(postResponse);
jArrayResponse.Merge(userJArray);
}
}
responseContent = jArrayResponse.ToString();
return responseContent;
}
}
I have an asp.net web API. (net framework 4.6.1) I am calling a 3rd party rest API (Product) in one of my actions. In functional tests, everything works as expected, no problem. But when I do spike tests (let's say, 100 users, 10 seconds ramp-up time) I am getting System.Threading.Tasks.TaskCanceledException: A task was canceled error after some successful responses. I googled and came across that it might be due to timeout. I added TimeSpan.FromSeconds(600); but still getting the same error.
Here is how I call this 3rd party API. Is there any problem with an async call? Would you please have a look at my code?
public class UtilitiesTest
{
private static readonly HttpClient _httpClient = new HttpClient();
//some not relevant code here
public static async Task<HttpResponseMessage> CallRazer(GameRequest gameRequest, string url)
{
try
{
FormUrlEncodedContent content = null;
if (url == "Product/")
{
try
{
//some code here
//Timeout
_httpClient.Timeout = TimeSpan.FromSeconds(600);
//Call Game
var response = await _httpClient.PostAsync("https://test.com/" + url, content);
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
content.Dispose();
}
}
else
{
try
{
//some code here
//Call Game
var response = await _httpClient.PostAsync("https://test.com/" + url, content);
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
content.Dispose();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
and here is how I call Razer:
private async Task<HttpResponseMessage> CallProducts()
{
//some code here
#region Call Razer for products
//**Call Razer**
var response = await Utilities.CallRazer(products, "Product/");
var htmlResponse = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<ProductResponseDto>(htmlResponse);
// some code here
return response;
}
Somebody in another forum, suggested me to use HTTPWeb Request instead of Httpclient. So I changed my code like below and all my troubles are gone.
//HTTPWebRequest
var request = (HttpWebRequest) WebRequest.Create("http://test.com" + url);
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
var keyValueContent = productRequest.ToKeyValue();
var formUrlEncodedContent = new FormUrlEncodedContent(keyValueContent);
var urlEncodedString = await formUrlEncodedContent.ReadAsStringAsync();
using (var streamWriter = new StreamWriter(await request.GetRequestStreamAsync()))
{
streamWriter.Write(urlEncodedString);
}
HttpWebResponse httpResponse = (HttpWebResponse) (await request.GetResponseAsync());
response = new HttpResponseMessage
{
StatusCode = httpResponse.StatusCode,
Content = new StreamContent(httpResponse.GetResponseStream()),
};
return response;
If it's still timing out after 600 seconds, then your web service just can't keep up.
But yes, a timeout will generate a TaskCanceledException, which is not intuitive at all. This will actually be changing in .NET Core at least (just fixed last week), but that doesn't help you.
Here is code I've used to rethrow a TimeoutException instead. The only other reason a TaskCanceledException could be thrown is if you passed a CancellationToken that ended up being cancelled, but you're not. So it's definitely a timeout.
} catch (TaskCanceledException) {
//Could have been caused by cancellation or timeout if you used one.
//If that was the case, rethrow.
//cancellationToken.ThrowIfCancellationRequested();
//HttpClient throws TaskCanceledException when the request times out. That's dumb.
//Throw TimeoutException instead and say how long we waited.
string time;
if (_httpClient.Timeout.TotalHours > 1) {
time = $"{_httpClient.Timeout.TotalHours:N1} hours";
} else if (_httpClient.Timeout.TotalMinutes > 1) {
time = $"{_httpClient.Timeout.TotalMinutes:N1} minutes";
} else if (_httpClient.Timeout.TotalSeconds > 1) {
time = $"{_httpClient.Timeout.TotalSeconds:N1} seconds";
} else {
time = $"{_httpClient.Timeout.TotalMilliseconds:N0} milliseconds";
}
throw new TimeoutException($"No response after waiting {time}.");
}
I found here https://stackoverflow.com/a/19215782/4332018 a nice solution to use CancellationToken with async HttpWebRequest:
public static class Extensions
{
public static async Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request, CancellationToken ct)
{
using (ct.Register(() => request.Abort(), useSynchronizationContext: false))
{
try
{
var response = await request.GetResponseAsync();
return (HttpWebResponse)response;
}
catch (WebException ex)
{
// WebException is thrown when request.Abort() is called,
// but there may be many other reasons,
// propagate the WebException to the caller correctly
if (ct.IsCancellationRequested)
{
// the WebException will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, ct);
}
// cancellation hasn't been requested, rethrow the original WebException
throw;
}
}
}
}
But I don't understand how I can abort request if it is performed longer than preset time.
I know about CancellationTokenSource() and CancelAfter(Int32), but don't understand how to modify the above example to use CancellationTokenSource, because it hasn't Register method.
How can I make a async HttpWebRequest with the possibility of cancellation after preset time?
When you create the token source, set the cancellation. Then pass in the token. It should timeout.
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(1000);
var ct = cts.Token;
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://www.zzz.com/here");
var test = Extensions.GetResponseAsync(httpWebRequest, ct);
I hope it will be helpfull for you
_cancelTasks = new CancellationTokenSource();
string Response = null;
var task = new Task(() => {
try
{
using (var wb = new WebClient())
{
var data = new NameValueCollection();
data["XMLString"] = XMLRequest;
var response = wb.UploadValues(ServiceURL, "POST", data);
}
}
catch (Exception ex)
{
}
}, _cancelTasks.Token);
task.Start();
if (!task.Wait(GWRequestTimeout * 1000))
{
_cancelTasks.Cancel();
}
I have the following method, on a windows-store project, to upload a file
public async Task<Boolean> UploadFileStreamService(Stream binaries, String fileName, String filePath)
{
try
{
filePath = Uri.EscapeDataString(filePath);
using (var httpClient = new HttpClient { BaseAddress = Constants.baseAddress })
{
var content = new StreamContent(binaries);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", App.Current.Resources["token"] as string);
App.Current.Resources["TaskUpload"] = true;
using (var response = await httpClient.PostAsync("file?fileName=" + filePath, content))
{
string responseData = await response.Content.ReadAsStringAsync();
if (responseData.Contains("errorCode"))
throw new Exception("Exception: " + responseData);
else
{
JsonObject jObj = new JsonObject();
JsonObject.TryParse(responseData, out jObj);
if (jObj.ContainsKey("fileId"))
{
if (jObj["fileId"].ValueType != JsonValueType.Null)
{
App.Current.Resources["NewVersionDoc"] = jObj["fileId"].GetString();
}
}
}
return true;
}
}
}
catch (Exception e)
{
...
}
}
And on the app.xaml.cs i have on the constructor:
NetworkInformation.NetworkStatusChanged +=
NetworkInformation_NetworkStatusChanged; // Listen to connectivity changes
And on that method i check for the connection changes.
What i would like to know is how to stop a upload task when i detect that network change ( from having internet to not having).
You can use cancellation tokens. You need CancellationTokenSource:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
Then pass token to your UploadFileStreamService method (use _cts.Token to get token):
public async Task<Boolean> UploadFileStreamService(Stream binaries, String fileName, String filePath, CancellationToken ct)
And use another overload of PostAsync which accepts token (note - also use overloads that accept tokens for all other async methods where possible, for example for ReadAsStringAsync):
using (var response = await httpClient.PostAsync("file?fileName=" + filePath, content, ct))
Then when you found network connection is lost, cancel with:
_cts.Cancel();
Note that this will throw OperationCancelledException on PostAsync call, which you may (or may not) want to handle somehow.