use async /await with synchronous methods - c#

How to make such piece of code run asynchronously with synchronous System.Xml.Serialization.XmlSerializer.Deserialize method which forces me to use Result all the way long?
...
await GetContent(url)
...
private async Task<Node> GetContent (string url)
{
var response = _httpClient.GetAsync(url).Result;
var ser = new XmlSerializer(typeof(Node));
retVal = (Node)ser.Deserialize(response.Content.ReadAsStreamAsync().Result);
}

The method is already async so there's no reason to use .Result. Just use await and remember to close the stream, otherwise the server connection remains open:
private async Task<Node> GetContent (string url)
{
var response = await _httpClient.GetAsync(url);
//**IMPORTANT** Ensure the stream is closed
using(var stream= await response.Content.ReadAsStreamAsync())
{
var ser = new XmlSerializer(typeof(Node));
var retVal = (Node)ser.Deserialize(stream);
return retVal;
}
}

Related

Null message on hTTPResponse.Content.ReadAsStringAsync().Result after 1st foreach loop

I have issue with null result messages when calling a HttpClient getAsync within a foreach loop.
I need to iterate with a list of objects for values to call an API via HttpClient. After the first loop, the HttpResponseMessage's result() comes up with null message.
I tried CacheControl.NoCache = true. It's not working.
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
/*This is where the issue is .begin.*/
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
/*This is where the issue is .end.*/
if (hTTPResponse.IsSuccessStatusCode)
{
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}
On: 'string result = hTTPResponse.Content.ReadAsStringAsync().Result;'
I would expect the result message would have values as it loops through ListOfObjects.
Update: Per comment
First off, I generally try to avoid using .Result with tasks. I also don't think adding the cache headers is the issue since you are making a server to server call.
One thing I tend to always do is evaluate the response before I try to read it. I also prefer to use the HttpRequestMessage so I can manage the disposal of it.
Here is a quick example demonstrating how call out:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Testing
{
class Program
{
static void Main(string[] args)
{
TestAsync().Wait();
}
static async Task TestAsync()
{
var urls = new string[]
{
"https://stackoverflow.com/questions/57084989/null-message-on-httpresponse-content-readasstringasync-result-after-1st-foreac",
"https://stackoverflow.com/users/2025711/rogala"
};
using (var httpClient = new HttpClient())
{
foreach (var url in urls)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
Console.WriteLine($"Request: {url}".PadLeft(5,'*').PadRight(5, '*'));
var response = await httpClient.SendAsync(request)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
Console.WriteLine($"{body.Length}{Environment.NewLine}");
}
else
{
Console.WriteLine($"*Bad request: {response.StatusCode}");
}
}
}
}
Console.ReadKey();
}
}
}
One additional thing to keep in mind is socket exhaustion. If this code is running on a server, then you will run into socket exhaustion once your service gets some load. I would highly recommend using an HttpClientFactory to handle the HttpClients.
With respect to why the content string is empty, it could be because the server didn't return content which could be a server issue. The server may have also throttled you, which resulted in no content. I would recommend looking into Polly to handle certain response codes.
Solved it... I moved the .DefaultRequestHeaders values inside the foreach() loop.
I suspected that they were pilling up at every loop iteration. I had to clear them and set them up before the GetAsync()
Updated code here:
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
/*Moved Code .begin.*/
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
/*Moved Code .end.*/
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
if (hTTPResponse.IsSuccessStatusCode)
{
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}

Cancel HttpClient request on connection lost

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.

WriteToStreamAsync cancel does not work

I am running a Task, which copies from one stream to another. This works without problems, including progress reporting. But i cant cancel the task. If i fire the CancellationToken, the copy progress runs till its completion, then the task is cancelled, but this is of course to late. Here is my actual code
private async Task Download(Uri uriToWork, CancellationToken cts)
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage requestAction = new HttpRequestMessage();
requestAction.Method = new HttpMethod("GET");
requestAction.RequestUri = uriToWork;
HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);
using (Stream streamToRead = (await httpResponseContent.Content.ReadAsInputStreamAsync()).AsStreamForRead())
{
string fileToWrite = Path.GetTempFileName();
using (Stream streamToWrite = File.Open(fileToWrite, FileMode.Create))
{
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload);
await streamToWrite.FlushAsync();
//streamToWrite.Dispose();
}
await streamToRead.FlushAsync();
//streamToRead.Dispose();
}
httpClient.Dispose();
}
Can someone help me please, or can explain, why it does not work?
Is it this operation that continues until it completes ?
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload);
Or is it this one ?
await streamToWrite.FlushAsync();
I think the latter needs probably to have the CancellationToken as well:
await streamToWrite.FlushAsync(cts);
Unfortunately I cannot answer why this cancel does not occur. However, a solution that consists in writing the Stream in chunks may help.
Here is something very dirty that works:
private async Task Download(Uri uriToWork, CancellationToken cts) {
using(HttpClient httpClient = new HttpClient()) {
HttpRequestMessage requestAction = new HttpRequestMessage();
requestAction.Method = new HttpMethod("GET");
requestAction.RequestUri = uriToWork;
HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);
string fileToWrite = Path.GetTempFileName();
using(Stream streamToWrite = File.Open(fileToWrite, FileMode.Create)) {
// Disposes streamToWrite to force any write operation to fail
cts.Register(() => streamToWrite.Dispose());
try {
await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, p);
}
catch(TaskCanceledException) {
return; // "gracefully" exit when the token is cancelled
}
await streamToWrite.FlushAsync();
}
}
}
I enclosed the httpClient in a using so a return disposes it properly.
I removed the streamToRead which was not used at all
Now here is the horror: I added a delegate that executes when the token is cancelled: it disposes streamToWrite while it is written to (ughhhh), which triggers an TaskCancelledException when WriteToStreamAsync cannot longer write in this disposed stream.
Please dont throw a puke bag at me yet, I am not experienced enough in this "Universal" Framework which looks very different as the usual one.
Here is a chunked stream solution that looks more acceptable. I shortened a bit the original code and added the IProgress as a parameter.
async Task Download(Uri uriToWork, CancellationToken cts, IProgress<int> progress) {
using(HttpClient httpClient = new HttpClient()) {
var chunkSize = 1024;
var buffer = new byte[chunkSize];
int count = 0;
string fileToWrite = Path.GetTempFileName();
using(var inputStream = await httpClient.GetInputStreamAsync(uriToWork)) {
using(var streamToRead = inputStream.AsStreamForRead()) {
using(Stream streamToWrite = File.OpenWrite(fileToWrite)) {
int size;
while((size = await streamToRead.ReadAsync(buffer, 0, chunkSize, cts).ConfigureAwait(false)) > 0) {
count += size;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => progress.Report(count));
// progress.Report(count);
await streamToWrite.WriteAsync(buffer, 0, size, cts).ConfigureAwait(false);
}
}
}
}
}
}
The blocking operation is most probably not WriteToStreamAsync() but FlushAsync(), so #Larry's assumption should be right, the FlushAsync method needs the cancellation token as well.

How to return a Task<T> in an async method

I have this method in my Windows Phone 8 app where I get some data from a url
public async static Task<byte[]> getData(string url)
{
HttpClient client = null;
HttpResponseMessage response = null;
Stream stream = null;
byte[] dataBytes = null;
bool error = false;
try
{
Uri uri = new Uri(url);
client = new HttpClient();
response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
if (dataBytes == null)
{
error = true;
}
else if (dataBytes.Length == 0)
{
error = true;
}
}
catch (HttpRequestException )
{
}
if (error)
{
return getData(url); // this is where the issue is
}
return dataBytes;
}
But since the method is an async one, the return type cannot be a Task, like I have done on the line return getData(url); since getData(string) returns Task. Any ideas on how I can rewrite this to make it work?
Awaiting the result of getData may do the trick. Still, I strongly recommand you to rewrite your method with a loop, rather than recursively call the method again. It makes it hard to read, and may lead to unforeseen issues.
public async static Task<byte[]> getData(string url)
{
bool success = false;
byte[] dataBytes = null;
while (!success)
{
try
{
Uri uri = new Uri(url);
var client = new HttpClient();
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
success = dataBytes != null && dataBytes.Length > 0;
}
catch (HttpRequestException)
{
}
}
return dataBytes;
}
you can get around the compile error by adding changing the return to the following :
if (error)
{
return await getData(url); // this is where the issue is
}
I hope you do realize that this code will keep on looping as long as no data is returned? having many clients like this could easily overload your server.

How to block caller thread after await operator

I'm using new HttpClient class to upload some data to the server, so I was forced to use new operators like await/async. I have following issue: how can I block a caller thread to wait until first file will be uploaded and after that move to the next one.
public async Task Upload(string filename)
{
HttpRequestMessage message = new HttpRequestMessage();
StreamContent streamContent = new StreamContent(new FileStream(filename, FileMode.Open));
message.Method = HttpMethod.Put;
message.Content = streamContent;
message.RequestUri = new Uri(webURI);
HttpResponseMessage response = await httpClient.SendAsync(message);
//I want to reach this point untill I will start to upload next file
if (response.IsSuccessStatusCode)
{
//do something
}
}
void uploadFiles()
{
foreach (string filename in filenames)
{
Upload(filename);
}
}
Thanks for any attention about my problem.
await that the upload has finished before you upload the next file:
async void uploadFiles()
{
foreach (string filename in filenames)
{
await Upload(filename);
}
}

Categories