Why is ReadAsStringAsync async? - c#

So, in the following snippet, why is ReadAsStringAsync an async method?
var response = await _client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Originally I expected SendAsync to send the request and load the response stream into memory at which point reading that stream would be in-process CPU work (and not really async).
Going down the source code rabbit hole, I arrived at this:
int count = await _stream.ReadAsync(destination, cancellationToken).ConfigureAwait(false);
https://github.com/dotnet/corefx/blob/0aa654834405dcec4aaa9bd416b2b31ab8d3503e/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs#L967
This makes me think that maybe the connection is open until the response stream is actually read from some source outside of the process? I fully expect that I am missing some fundamentals regarding how streams from Http Connections work.

SendAsync() waits for the request to finish and the response to start arriving.
It doesn't buffer the entire response; this allows you to stream large responses without ever holding the entire response in memory.

Related

Async code only seems works only when a breakpoint is inserted

I have the following C# code:
var response = client.GetAsync(uri).Result;
MemoryStream stream = new MemoryStream();
response.Content.CopyToAsync(stream);
System.Console.WriteLine(stream.Length);
When I insert a breakpoint before the first statement and then continue the program, the code works fine and over 4 MB of data gets stored in the stream.
But if I run the program without any breakpoints or inserting the breakpoint after the after the first statement shown above, the code runs but no data or only 4 KB of data gets stored in the stream.
Can someone please explain why this is happening?
Edit:
Here is what I am trying to do in my program. I use couple of HttpClient.PostAsync requests to get a uri to download a wav file. Then I want to download the wav file into a memory stream. I don't know of any other ways to do this yet.
It seems like you are basically messing the flow of async and await.
The async call will be waited to complete and recapture the task when you use await keyword.
The mentioned code does not clarify whether you are using the async signature in your method or not. Let me clarify the solution for you
Possible solution 1:
public async Task XYZFunction()
{
var response = await client.GetAsync(uri); //we are waiting for the request to be completed
MemoryStream stream = new MemoryStream();
await response.Content.CopyToAsync(stream); //The call will wait until the request is completed
System.Console.WriteLine(stream.Length);
}
Possible solution 2:
public void XYZFunction()
{
var response = client.GetAsync(uri).Result; //we are running the awaitable task to complete and share the result with us first. It is a blocking call
MemoryStream stream = new MemoryStream();
response.Content.CopyToAsync(stream).Result; //same goes here
System.Console.WriteLine(stream.Length);
}

SendAsync and CopyToAsync not working when downloading a large file

I have a small app that receives a request from a browser, copy the header received and the post data (or GET path) and send it to another endpoint.
It then waits for the result and sends it back to the browser. It works like a reverse proxy.
Everything works fine until it receives a request to download a large file. Something like a 30MB will cause an strange behaviour in the browser. When the browser reaches around 8MB it stops receiving data from my app and, after some time, it aborts the download. Everything else works just fine.
If I change the SendAsync line to use HttpCompletionOption.ResponseContentRead it works just fine. I am assuming there is something wrong waiting for the stream and/or task, but I can't figure out what is going on.
The application is written in C#, .net Core (latest version available).
Here is the code (partial)
private async Task SendHTTPResponse(HttpContext context, HttpResponseMessage responseMessage)
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
context.Response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(context.Response.Body);
}
}
public async Task ForwardRequestAsync(string toHost, HttpContext context)
{
var requestMessage = this.BuildHTTPRequestMessage(context);
var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
await this.SendHTTPResponse(context, responseMessage);
}
EDIT
Changed the SendHTTPResponse to wait for responseMessage.Content.ReadAsStreamAsync using await operator.
Just a guess but I believe the issue lies with the removal of the transfer encoding:
context.Response.Headers.Remove("transfer-encoding");
If the http request you are making with _httpClient returns the 30MB file using Chunked encoding (target server doesn't know the file size) then you would need to return the file to the browser with Chunked encoding as well.
When you buffer the response on your webservice (by passing HttpCompletionOption.ResponseContentRead) you know the exact message size you are sending back to the browser so the response works successfully.
I would check the response headers you get from responseMessage to see if the transfer encoding is chunked.
You are trying to stream a file but you are doing it not exactly right. If you do not specify,ResponseHeadersRead, response will never come back unless the server ends the request because it will try to read the response till the end.
HttpCompletionOption enumeration type has two members and one of them is ResponseHeadersRead which tells the HttpClient to only read the headers and then return back the result immediately.
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var stream = await response.Content.ReadAsStreamAsync();
using (var reader = new StreamReader(stream)) {
while (!reader.EndOfStream) {
//Oh baby we are streaming
//Do stuff copy to response stream etc..
}
}
Figure 3 shows a simple example where one method blocks on the result of an async method. This code will work just fine in a console application but will deadlock when called from a GUI or ASP.NET context. This behavior can be confusing, especially considering that stepping through the debugger implies that it’s the await that never completes. The actual cause of the deadlock is further up the call stack when Task.Wait is called.
Figure 3 A Common Deadlock Problem When Blocking on Async Code
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait();
}
}
The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.
Note that console applications don’t cause this deadlock. They have a thread pool SynchronizationContext instead of a one-chunk-at-a-time SynchronizationContext, so when the await completes, it schedules the remainder of the async method on a thread pool thread. The method is able to complete, which completes its returned task, and there’s no deadlock. This difference in behavior can be confusing when programmers write a test console program, observe the partially async code work as expected, and then move the same code into a GUI or ASP.NET application, where it deadlocks.
The best solution to this problem is to allow async code to grow naturally through the codebase. If you follow this solution, you’ll see async code expand to its entry point, usually an event handler or controller action. Console applications can’t follow this solution fully because the Main method can’t be async. If the Main method were async, it could return before it completed, causing the program to end. Figure 4 demonstrates this exception to the guideline: The Main method for a console application is one of the few situations where code may block on an asynchronous method.
Figure 4 The Main Method May Call Task.Wait or Task.Result
class Program
{
static void Main()
{
MainAsync().Wait();
}
static async Task MainAsync()
{
try
{
// Asynchronous implementation.
await Task.Delay(1000);
}
catch (Exception ex)
{
// Handle exceptions.
}
}
}
LEARN MORE HERE
try these.
using (HttpResponseMessage responseMessage= await client.SendAsync(request))
{
await this.SendHTTPResponse(context, responseMessage);
}
or
using (HttpResponseMessage responseMessage=await _httpClient.SendAsync(requestMessage,
HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
await this.SendHTTPResponse(context, responseMessage)
}

Using the HttpClient how can I view the request content body?

I'd like to see what the request body of my HTTP request contained for debugging purposes. Here's what I've got right now:
var httpResponseMessage = await _httpClient.PostAsync(uri, objectToInsert, jsonFormatter);
//This throws an exception
var thisDoesntWork = await httpResponseMessage.RequestMessage.Content.ReadAsStringAsync();
This throws an ObjectDisposedException. How can I view the request body to make sure the JSON being sent is correct?
The short answer is you can't after the request has already been sent. HttpClient disposes the content body after the request is made as a convenience to the developer. See Why do HttpClient.PostAsync and PutAsync dispose the content? for some detail.
As an alternative, you could inherit from an existing HttpContent implementation, override the Dispose method, and log the body of the content prior to disposal.
Or, as suggested, use an external tool to monitor the request in flight.
You'd need to first create a HttpRequestMessage to pass to the HttpClient. Then you can just evaluate/log the content.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content=JsonSerializer.Serialize(objectToInsert);
Console.WriteLine(request.Content.ReadAsStringAsync().Result);
HttpResponseMessage response = await httpClient.SendAsync(request);
ObjectDisposedException is thrown because you're disposing the HttpRequestMessage and HttpClient before Content.ReadAsStringAsync() finishes.
Note that Content.ReadAsStringAsync() is an asynchronous method. You need to wait for it to complete before disposing the HttpClient.
If you are not wanting to use async you can add .Result to force the code to execute synchronously:
var response = httpClient.PostAsync(BaseUri, new FormUrlEncodedContent(parameters)).Result;
var contents = response.Content.ReadAsStringAsync().Result;

Does HttpClient spin a new thread even if I immediately get the Result of the returned task?

In my application I like to try and be consistant and use HttpClient whenever I can. However, sometimes I dont need the asynchronous properties of HttpClient and so I simply get the Result of the Task as soon as it is returned as demonstrated in the code below.
public HttpResponseMessage httpPostWrapperMethod(string postBody, string url)
{
HttpContent content = new StringContent(postBody, Encoding.UTF8, "application/json");
HttpClient client = new HttpClient();
return client.PostAsync(url, content).Result;
}
My two part question is this:
Does this code cause a new thread to be spun in the background when making the call?
and
If my calls to this other service are taking around 500ms, is this going to cause me to eat up too many threads when the service is under a production load of around 100 requests/second?
PostAsync doesn't result in a thread being created, no. In fact, were the code to be used asynchronously no thread would ever need to be doing anything for this work to be done. In your case here you're having the current thread sit around doing nothing while you wait for the operation to finish, so that is the only thread who's time is being consumed/wasted when performing this operation.

Should I Use Async Processing?

I've got a Windows service that monitors a table (with a timer) for rows, grabs rows one at a time when they appear, submits the information to a RESTful web service, analyzes the response, and writes some details about the response to a table. Would I gain anything by making this async? My current (stripped down) web service submission code is as follows:
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException we)
{
resp = (HttpWebResponse)we.Response;
}
if (resp != null)
{
Stream respStream = resp.GetResponseStream();
if (respStream != null)
{
responseBody = new StreamReader(respStream).ReadToEnd();
}
resp.Close();
respStream.Close();
}
return responseBody;
If you don't care how long it takes to get your response for any individual request, no, there's no particular reason to make it async.
On the other hand, if you're waiting for one request to fully finish before you start your next request you might have trouble handling a large volume. In this scenario you might want to look at parallelizing your code. But that's only worth discussing if you get large numbers of items entered into the database for processing.
By using asynchronous methods you avoid the need to have a dedicated thread blocking on the results of the async operation. You can free up that thread to work on other more productive tasks until the async task has finished and is again productive work.
If your service is not in high demand, and is not frequently in a position where you have more work that needs doing than threads to do it, then you don't need to worry. For many business apps they simply have so low usage rates that this isn't needed.
If you have a large scale app servicing enough users that you have a very high number of concurrent requests (even if it's just at peak usage times) then it may be work switching to asynchronous counterparts.

Categories

Resources