What's the "right way" to use HttpClient synchronously? - c#

I used quote marks around "right way" because I'm already well aware that the right way to use an asynchronous API is to simply let the asynchronous behavior propagate throughout the entire call chain. That's not an option here.
I'm dealing with a very large and complicated system designed specifically to do batch processing synchronously in a loop.
The reason why suddenly I'm using HttpClient is because prior to now all data for the batch processing was gathered from a SQL database, and now we're adding a Web API call to the mix.
Yes, we're calling a Web API in a synchronously executing loop. I know. Rewriting the whole thing to be async just isn't an option. This is actually what we want to do. (We're minimizing the number of API calls as much as possible)
I actually did try to propagate the async behavior up the call chain, but then I found myself 50 files deep in changes, still with hundreds of compiler errors to resolve, and lost all hope. I am defeated.
So then, back to the question, given Microsoft's recommendation to never use WebRequest for new development and to instead use HttpClient, which offers only an asynchronous API, what am I to do?
Here is some pseudo-code of what I'm doing...
foreach (var thingToProcess in thingsToProcess)
{
thingToProcess.ProcessStuff(); // This makes an API call
}
How do I implement ProcessStuff()?
My first implementation looked like this
public void ProcessStuff()
{
var apiResponse = myHttpClient // this is an instance of HttpClient
.GetAsync(someUrl)
.Result;
// do some stuff with the apiResponse
}
I was told however, that calling .Result in this manner can result in deadlocks when it's called from something like ASP.NET due to the synchronization context.
Guess what, this batch process will be kicked off from an ASP.NET controller. Yes, again, I know, this is silly. When it runs from ASP.NET it's only "batch processing" one item instead of the whole batch, but I digress, it still gets called from ASP.NET and thus I'm concerned about deadlocks.
So what's the "right way" to handle this?

Try the following:
var task = Task.Run(() => myHttpClient.GetAsync(someUrl));
task.Wait();
var response = task.Result;
Use it only when you cannot use an async method.
This method is completely deadlock free as mentioned on the MSDN blog:
ASP.Net–Do not use Task .Result in main context.

For anyone coming across this now, .NET 5.0 has added a synchronous Send method to HttpClient. https://github.com/dotnet/runtime/pull/34948
You can therefore use this instead of SendAsync. For example
public string GetValue()
{
var client = new HttpClient();
var webRequest = new HttpRequestMessage(HttpMethod.Post, "http://your-api.com")
{
Content = new StringContent("{ 'some': 'value' }", Encoding.UTF8, "application/json")
};
var response = client.Send(webRequest);
using var reader = new StreamReader(response.Content.ReadAsStream());
return reader.ReadToEnd();
}
This code is just a simplified example, it's not production ready.

You could also look at using Nito.AsyncEx, which is a nuget package. I've heard of issues with using Task.Run() and this this addresses that. Here's a link to the api docs:
http://dotnetapis.com/pkg/Nito.AsyncEx/4.0.1/net45/doc/Nito.AsyncEx.AsyncContext
And here's an example for using an async method in a console app:
https://blog.stephencleary.com/2012/02/async-console-programs.html

Related

Any issue if no await on HttpClient().GetAsync().Result.Content.ReadAsStringAsync().Result?

Any issue with this C# method (.Net Framework 4.8) with not having await? I am using ReadAsStringAsync() with no await. Caller of RetrieveContent() method can't call it asynchronously. So, I need to avoid making it async method. This is a Windows Console app that can also be installed as a Windows Service- with an endpoint listening for requests. It is supposed to do Synchronous processing- one request at a time.
public string RetrieveContent()
{
return new HttpClient().GetAsync("https://www.google.com").Result.Content.ReadAsStringAsync().Result;
}
OR
public string RetrieveContent()
{
var response = new HttpClient().GetAsync("https://www.google.com").Result;
return response.Content.ReadAsStringAsync().Result;
}
Updates:
I can change like this. Thus, caller to this method doesn't need an information from Async method. Will this cause a dead-lock too?
public void RetrieveContent() //this is just logging content
{
var response = new HttpClient().GetAsync("https://www.google.com").Result;
if (response.StatusCode == HttpStatusCode.OK)
_logger.LogInformation($"content: {response.Content.ReadAsStringAsync().Result} "); //_logger is logging to local disk I/O
else
_logger.LogError($"HttpStatusCode: {response.StatusCode} ");
}
'''
Calling Result from synchronous code blocks is considered unsafe and may cause deadlocks because the task might depend on other incomplete tasks. Instead, you should usually be striving to make caller methods async as much as possible. In this specific case, however, it may be okay with a Task.Run wrapper.
See this question for more details: What's the "right way" to use HttpClient synchronously?

What's the difference between Response.WriteAsync and returning a string

Hi I was wandering if someone can explain to me the difference between these two methods for returning data from a controller.
Is there an advantage or reason for using on method over the other?
I am guessing that the returning function just calls Response.WriteAsync further down the line but I am not sure.
Using postman both methods return the exact same response, so I was just curious about the two options, is there a reason for using one over the other or just personal preference.
Between Response.WriteAsync:
[HttpGet("Fetch_WriteAsync")]
public async Task Fetch_AsyncWrite()
{
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { data = "my_fetched_data" }));
}
and just returning:
[HttpGet("Fetch_Return")]
public async Task<string> Fetch_Return()
{
HttpContext.Response.ContentType = "application/json";
return await Task.Factory.StartNew(()=>JsonConvert.SerializeObject(new { data = "my_fetched_data" }));
}
Basically there is a difference regarding how your code is executed under the hood. In the first case, in which you have this
await HttpContext.Response
.WriteAsync(JsonConvert.SerializeObject(new
{
data = "my_fetched_data"
}));
an ASP.NET thread would be used to execute your code.
While in the second case, in which you have this
return await Task.Factory
.StartNew(()=>JsonConvert.SerializeObject(new
{
data = "my_fetched_data"
}));
a thread pool thread would be used.
As it is stated here:
You can use the ThreadPool in exactly the same way in ASP.NET and it
works just as you would expect. The problem is not in the ThreadPool
itself but in what else ASP.NET uses it for at the same time. ASP.NET
is multi-threaded by design and it uses the ThreadPool to serve pages
and content mapped to the ASP.NET ISAPI filter.
If you also use the ThreadPool, then ASP.NET has fewer threads to
utilize and requests are put on hold until the pool returns a free
thread. This might not be a problem for a low traffic site, but more
popular sites can get into trouble. Low traffic sites can get into
trouble if they use the ThreadPool a lot.
That being said, I would opt for the first solution.

Task using httpclient and returning a value not working correctly

I've looked around and followed a few tutorials on how to set this up and I'm having no luck. I'm building a cross platform app using Xamarin. I have a portable class library to handle all my business logic and web service calls.
The issue I'm having is when I run the code within the method below it works without an issue however if I run it as shown below as a task it takes a long time. I believe its deadlocked but as I'm new to this I can't understand why. I want my Api calls to be in the PCL so I can share it between each platfrom.
public async Task<LoginResponse> Login(string userName, string password)
{
HttpClient client = new HttpClient();
string baseUrl = "http://someURLhere/";
client.BaseAddress = new Uri(baseUrl);
string authData = string.Format("/IOSLogin?Username={0}&Password={1}&languageSettingOnDevice={2}", userName, password, "en");
var uri = new Uri(string.Format("{0}{1}", baseUrl, authData));
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
LoginResponse jsonResponse = JsonConvert.DeserializeObject<LoginResponse>(content);
jsonResponse.loginSuccesfull = true;
return jsonResponse;
}
else
{
return new LoginResponse() { loginSuccesfull = false };
}
}
In the project for the ios version, on a button event I use this code to run the task shown above.
Core.Service.AtmisService service = new Core.Service.AtmisService();
LoginResponse loginTask = service.Login(userName.Text, password.Text).Result;
I thought that by setting it up this way when I use .Result at the end of call to the task it would execute this task and return the result. I have added breakpoints and it enters the login method and gets as far as this var response = await client.GetAsync(uri); and then it simply does nothing. Any ideas on what I am doing wrong. Thank you for any help.
Referencing this article Async/Await - Best Practices in Asynchronous Programming
“Async all the way” means that you shouldn’t mix synchronous and
asynchronous code without carefully considering the consequences. In
particular, it’s usually a bad idea to block on async code by calling
Task.Wait or Task.Result. This is an especially common problem for
programmers who are “dipping their toes” into asynchronous
programming, converting just a small part of their application and
wrapping it in a synchronous API so the rest of the application is
isolated from the changes. Unfortunately, they run into problems with
deadlocks. After answering many async-related questions on the MSDN
forums, Stack Overflow and e-mail, I can say this is by far the
most-asked question by async newcomers once they learn the basics:
“Why does my partially async code deadlock?”
Don’t mix blocking and async code. You should go Async all the way. Your deadlock is because the .Result is blocking.
you want to do...
LoginResponse loginTask = await service.Login(userName.Text, password.Text);
To summarize this second guideline, you should avoid mixing async and
blocking code. Mixed async and blocking code can cause deadlocks,
more-complex error handling and unexpected blocking of context
threads. The exception to this guideline is the Main method for
console applications, or—if you’re an advanced user—managing a
partially asynchronous codebase.
In Your case using a button EventHandler you would do something like this:
private async void myButtonItem_Clicked(object sender, EventArgs e)
{
Core.Service.AtmisService service = new Core.Service.AtmisService();
LoginResponse loginTask = await service.Login(userName.Text, password.Text).ConfigureAwait(false);
//The rest of the code in this method may run in a different thread than the one that invoked the method. If you have ui code after this then avoid using ConfigureAwait(false)
}
You can find more information on avoiding deadlocks here.

Async call with await in HttpClient never returns

I have a call I am making from inside a xaml-based, C# metro application on the Win8 CP; this call simply hits a web service and returns JSON data.
HttpMessageHandler handler = new HttpClientHandler();
HttpClient httpClient = new HttpClient(handler);
httpClient.BaseAddress = new Uri("http://192.168.1.101/api/");
var result = await httpClient.GetStreamAsync("weeklyplan");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(WeeklyPlanData[]));
return (WeeklyPlanData[])ser.ReadObject(result);
It hangs at the await but the http call actually returns almost immediately (confirmed through fiddler); it is as if the await is ignored and it just hangs there.
Before you ask - YES - the Private Network capability is turned on.
Any ideas why this would hang?
Check out this answer to my question which seems to be very similar.
Something to try: call ConfigureAwait(false) on the Task returned by GetStreamAsync(). E.g.
var result = await httpClient.GetStreamAsync("weeklyplan")
.ConfigureAwait(continueOnCapturedContext:false);
Whether or not this is useful depends on how your code above is being called - in my case calling the async method using Task.GetAwaiter().GetResult() caused the code to hang.
This is because GetResult() blocks the current thread until the Task completes. When the task does complete it attempts to re-enter the thread context in which it was started but cannot because there is already a thread in that context, which is blocked by the call to GetResult()... deadlock!
This MSDN post goes into a bit of detail on how .NET synchronizes parallel threads - and the answer given to my own question gives some best practices.
Just a heads up - if you miss the await at the top level in an ASP.NET controller, and you return the task instead of the result as a response, it actually just hangs in the nested await call(s) with no errors. A silly mistake, but had I seen this post it might have saved me some time checking through the code for something odd.
Disclaimer: I don't like the ConfigureAwait() solution because I find it non-intuitive and hard to remember. Instead I came to the conclusion to wrap non awaited method calls in Task.Run(() => myAsyncMethodNotUsingAwait()). This seems to work 100% but might just be a race condition!? I'm not so sure what is going on to be honest. This conclusion might be wrong and I risk my StackOverflow points here to hopefully learn from the comments :-P. Please do read them!
I just had the problem as described and found more info here.
The statement is: "you can't call an asynchronous method"
await asyncmethod2()
from a method that blocks
myAsyncMethod().Result
In my case I couldn't change the calling method and it wasn't async. But I didn't actually care about the result. As I remember it also didn't work removing the .Result and have the await missing.
So I did this:
public void Configure()
{
var data = "my data";
Task.Run(() => NotifyApi(data));
}
private async Task NotifyApi(bool data)
{
var toSend = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
await client.PostAsync("http://...", data);
}
In my case I didn't care about the result in the calling non-async method but I guess that is quite common in this use case. You can use the result in the calling async method.

Why do HTTP requests never return with async await?

I am trying to conduct an HTTP call to another server from within an ASP.NET application run on IIS 8.5.
To get started, I took some hints from an article by Microsoft, Call a Web API From a .NET Client (C#).
I could easily see a pattern of how they make HTTP calls there; to show just one shortened example:
static async Task<Product> GetProductAsync(string path)
{
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
// retrieve response payload
... = await response.Content.ReadAsAsync<...>();
}
// do something with data
}
Easy enough, I thought, so I quickly wrote a similar method for my application (note that the ReadAsAsync extension method appears to require an additional library, so I chose one of the built-in, more abstract, but otherwise presumeably analogous methods):
private async Task<MyInfo> RetrieveMyInfoAsync(String url)
{
var response = await HttpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<MyInfo>(responseBody);
}
Unfortunately, calling this method will cause my application to hang. When debugging, it turns out that the await call to GetAsync never returns.
After searching around for a bit, I stumbled over a remotely similar issue, in whose comments section I found a very interesting suggestion by Mr. B:
Remove all the async stuff and make sure it works.
So I gave it a try:
private Task<MyInfo> RetrieveMyInfoAsync(String url)
{
return HttpClient.GetAsync(url).ContinueWith(response =>
{
response.Result.EnsureSuccessStatusCode();
return response.Result.Content.ReadAsStringAsync();
}).ContinueWith(str => JsonConvert.DeserializeObject<MyInfo>(str.Result.Result));
}
Somewhat surprisingly (to me), this works. GetAsync returns the expected response from the other server within less than a second.
Now, working with AngularJS at the same time, I am a bit disappointed by things like response.Result.Content and str.Result.Result. In AngularJS, I'd expect the above call to be simply something like:
$http.get(url).then(function (response) {
return response.data;
});
Even if we discount the automatic JSON deserialization that's happening in JavaScript, the AngularJS code is still easier as e.g. response is not wrapped into a promise or anything like that, nor will I end up with a structure like Task<Task<...>> when returning another promise from within the continuation function.
Therefore, I am not very happy with having to use this ContinuesWith syntax rather than the more readable async-await pattern, if the latter just worked.
What am I doing wrong in the async-await variant of my C# HTTP call?
So judging by the fact that ConfigureAwait(false) helped with your issue, please, read these from Stephen Cleary's blog:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
The guy's pretty much an async expert (he wrote the Concurrency in C# Cookbook book), so whatever I can say he probably explains better. Basically you're blocking the ASP.NET thread somewhere, maybe not using await all the way but rather Wait, Result or GetResult(). You should be able to diagnoze the issue yourself using that blog.
What ConfigureAwait(false) does is it does not capture the current context, so the HTTP request gets performed (correctly) somewhere else than on the ASP.NET context, preventing a deadlock.
EDIT:
GetAwaiter().GetResult() is what's causing the issue, judging by your comment. If you changed that to await and the calling method to async you'd probably fix everything.
Since C# 7.0 and async Task Main() method support there's really no reason to block instead of using await in your application code, ever.

Categories

Resources