I am new to the async world of C# and admittedly don't have a lot of knowledge on the subject. I just had to implement it in one of our services and I am a little confused on how it works. I want to POST to an API as fast as absolutely possible. I'm less interested in how long it takes to get the response and do stuff with it. Here's an example of what I'm doing.
foreach (var item in list)
{
callPostFunction(item.data);
log("Posted");
}
public async void callPostFunction(PostData data)
{
var apiResult = await postToAPI(data);
updateDB(apiResult);
}
public static async Task<string> postToAPI(PostData dataToSend)
{
var url = "Example.com";
HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(url);
ASCIIEncoding encoding = new ASCIIEncoding();
string postData = dataToSend;
byte[] dataBytes = encoding.GetBytes(postData);
httpWReq.Method = "POST";
httpWReq.ContentType = "application/x-www-form-urlencoded";
httpWReq.ContentLength = dataBytes.Length;
httpWReq.Accept = "application/json, application/xml, text/json, text/x-json, text/javascript, text/xml";
using (var stream = await httpWReq.GetRequestStreamAsync())
{
await stream.WriteAsync(dataBytes, 0, dataBytes.Length);
}
HttpWebResponse response = (HttpWebResponse)httpWReq.GetResponse();
return new StreamReader(response.GetResponseStream()).ReadToEnd();
}
What happens is if I put 1000 items in the list, "Posted" is logged 1000 times immediately. Great, the process is done and can continue doing other things. The problem is the somewhere in the background the callPostFunction is calling postToAPI and posting to the API, it works, but it takes a long time. It takes about as long (although not as long) as it did before implementing async. I feel like I'm missing something.
I have to hit the API as fast. Ideally I'd like to hit it as often as the callPostFunction is getting called, nearly immediately. How might I go about doing that?
Set ServicePointManager.DefaultConnectionLimit to int.MaxValue.
Also, as #Servy pointed out, avoid async void.
You're not missing something. It takes time to post a bunch of data. Network connections are slow. They have limited bandwidth. Doing this work asynchronously is no magic bullet for speed. It's purpose is not to optimize speed either.
Related
I have this method that downloads data from the internet:
public ObservableCollection<Magnetka> ParseJSON() {
ObservableCollection<Models.Magnetka> Markers = new ObservableCollection<Models.Magnetka>();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
request.Headers.Add("Authentication-Token", "(KEY)");
request.Method = "GET";
request.ContentType = "application/json";
CookieContainer cookieContainer = new CookieContainer();
request.CookieContainer = cookieContainer;
var response = request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
......... JSON parsing)
return Markers;
Now I found out this isn't too fast and it freezes the app for two seconds or so, so I need to make it async. However, I call the method like this in another class:
GetMarkers getMarkers = new GetMarkers();
Markers = getMarkers.ParseJSON();
How can I make my method async? I don't understand the concept well and if I make ParseJSON() async Task, I don't know where to put the "await" keyword.
Can anyone help me?
first, mark the method async and use the async version of GetResponse
public async Task<ObservableCollection<Magnetka>> ParseJSON()
{
...
var response = await request.GetResponseAsync();
...
return Markers;
}
then when you call it use the await keyword
Markers = await getMarkers.ParseJSON();
You would change your method to something like this instead:
public async Task<ObservableCollection<Magnetka>> ParseJSON()
{
var markers = new ObservableCollection<Magnetka>();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
request.Headers.Add("Authentication-Token", "(KEY)");
request.Method = "GET";
request.ContentType = "application/json";
CookieContainer cookieContainer = new CookieContainer();
request.CookieContainer = cookieContainer;
using var response = await request.GetResponseAsync().ConfigureAwait(false);
using var responseStream = response.GetResponseStream();
using var streamReader = new StreamReader(responseStream);
var json = await streamReader.ReadToEndAsync().ConfigureAwait(false);
// json parsing
return markers;
}
Firstly, you would mark your method async. This signals the compiler to set up a state machine for this method for continuation. Nothing async happening yet though.
Another thing you need to do is to wrap your return value in a Task, which is similar to a promise in some other languages. Tasks are await-able, which is where the asynchronous stuff is happening.
Switching from using the synchronous methods in WebRequest to use the Async variants and awaiting their results makes your method async.
So from:
request.GetResponse()
To:
await request.GetResponseAsync()
Which returns a awaitable Task<WebResponse>.
The same goes for the stream reader code, you can use its async API too.
A tricky thing by using asynchronous code is that, it kind of forces you to do this all the way up. However, there are exceptions for lifecycle methods and events, where you don't necessarily have to change the signature to return a Task.
Another thing you might notice here. I've added ConfigureAwait(false) at the end of the async method calls. I've done this because out of the box, after returning from an awaited method, the code will try to return to the originating context. This can lead to a couple of issues such as dead-locks if the originating context is the UI Thread. Or, it can lead to bad performance trying to switch between context a lot. Adding ConfigureAwait(false) will just return on the same context the await was run on, which is fine as long as we don't need or expect to return on that context.
As for where you call ParseJson() is probably equally important. Consider deferring it to some lifecycle method or event where you are not doing much else.
This could be OnResume() on Android or ViewWillAppear on iOS. If you are using MVVM, use a Command to encapsulate it.
When I try to make POST requests in Xamarin the time the function GetRequestStream() takes is 10-20 seconds. The answer time on my server is below 1 second and so is the POST request I made from a website.
I already tried:
Setting proxy server to null so it won't look it up
Using blocks around the requests so it get's flushed
Increased the maximum connections
Did everything with async multithreading
Even tried another class called RestSharp - same result.
Nothing I did was actually helping to reduce the runtime even by 100 milliseconds. I just cannot imagine that this is Xamarins fault because I can't be the only one who decided to do some HTTP requests in his cross platform app. I already lost UWP since the ServiceManager, which I use to connect to TLS sites isn't supported in UWP - thank you Xamarin.
I really need solutions so please help :)
this is the code i used and optimized:
byte[] byteArray = Encoding.UTF8.GetBytes(query);
HttpWebRequest webRequest = new HttpWebRequest(uri);
webRequest.Method = "POST";
webRequest.Proxy = null;
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Timeout = 1000;
webRequest.ReadWriteTimeout = 1000;
Stream dataStream = await webRequest.GetRequestStreamAsync();
await dataStream.WriteAsync(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse webResponse = await webRequest.GetResponseAsync();
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8))
{
string ret = await reader.ReadToEndAsync();
return ret;
}
and this is the code i tried with the ModernHttpClient
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient(new NativeMessageHandler());
Dictionary<string, string> values = new Dictionary<string, string>();
string[] q = query.Split('&');
for (int i = 0; i < q.Length; i++)
values.Add(q[i].Split('=')[0], q[i].Split('=')[1]);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
HttpResponseMessage response;
try
{
response = await client.PostAsync(uri, content);
string answer = await response.Content.ReadAsStringAsync();
return answer;
}
catch (Exception e)
{
return "";
}
and of course i added these lines before calling the whole network stuff
ServicePointManager.ServerCertificateValidationCallback += (a, b, c, d) => { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.Expect100Continue = false;
ServicePointManager.DefaultConnectionLimit = 100;
It's definitely a Xamarin or an Android Problem. I don't have other devices to test it,but... what can i do now? I tested it on a .Net console application, so it's definitely not .Net related.
I just found the answer!
it speeds up when i set the Protocol Version. It's mentione basically nowhere but i googled for android post slowdowns and there i found it, finally.
setting the protocol version to HTTP 1.1 speeds things up very fast!
now i just have to fix the boot delay with xamarin >.<
Thank you so much for you help!
Try implement ModernHttpClientPlugin into your code.
Just add this line when initializing new HttpClient, and everything should run smoother.
var httpClient = new HttpClient(new NativeMessageHandler());
Another idea is to try your implementation in new blank console aplication. Then you will see if this is problem with your server/client or Xamarin itself. It should look something like this.
class Program
{
static void Main(string[]args)
{
MainAsync(null);
Console.ReadKey();
}
static async System.Threading.Tasks.Task MainAsync(string[] args)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
Dictionary<string, string> values = new Dictionary<string, string>();
var query = "user=mickey&passwd=mini";
string[] q = query.Split('&');
for (int i = 0; i < q.Length; i++)
values.Add(q[i].Split('=')[0], q[i].Split('=')[1]);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
HttpResponseMessage response;
try
{
response = await client.PostAsync(new Uri("https://www.example.com/login.php"), content);
string answer = await response.Content.ReadAsStringAsync();
}
catch (Exception e)
{
}
}
}
Please, give us feedback how fast that goes.
In my case 'WebClient' and 'HttpWebReuest' cause first-time delays about 20 secs (possible, during resolve of new hosts) on some specific Android 7 devices, but works good on others.
I tried different ways to solve this problem but for me only replacement to 'HttpClient' solves it.
I am using this code to get the data from an icecast radio, but the ResponseStream stops reading data at 64K recieved. Can you help me with this?
HttpWebRequest request = (HttpWebRequest) WebRequest.Create("http://icecast6.play.cz/radio1-128.mp3");
request.AllowReadStreamBuffering = false;
request.Method = "GET";
request.BeginGetResponse(new AsyncCallback(GetShoutAsync), request);
void GetShoutAsync(IAsyncResult res)
{
HttpWebRequest request = (HttpWebRequest) res.AsyncState;
HttpWebResponse response = (HttpWebResponse) request.EndGetResponse(res);
Stream r = response.GetResponseStream();
byte[] data = new byte[4096];
int read;
while ((read = r.Read(data, 0, data.Length)) > 0)
{
Debug.WriteLine(data[0]);
}
}
I don’t see any obvious problems in your code. Apart from not using async-await which greatly simplifies the kind of asyncronous code you’re developing :-)
What do you mean “the ResponseStream stops reading”?
If the connection is dropped, then my #1 idea — server does that. Use wireshark to confirm, and then use wireshark to compare the request’s HTTP header with e.g. Winamp that starts playing that stream. I’m sure you’ll find some important differences.
If however it merely pauses, it’s normal.
Upon connect, streaming servers typically send you some initial amount of data, and then they only send you their data in real-time. So, after you’ve received that initial buffer, you’ll only get the data # the rate of your stream, i.e. 16 kbytes/sec for your 128 kbit/sec radio.
BTW, some clients send “Initial-Burst” HTTP header with the request, but I was unable to find the documentation about that header. When I worked on my radio for WP7, I basically replicated the behavior of some other, iOS app.
Finally I write this code to solve the issue, it is completely necessary to use the namespace : Windows.Web.Http, And it is like..
Uri url = new Uri("http://icecast6.play.cz/radio1-128.mp3");
HttpResponseMessage response = await httpClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead);
IInputStream inputStream = await response.Content.ReadAsInputStreamAsync();
try
{
ulong totalBytesRead =
IBuffer buffer = new Windows.Storage.Streams.Buffer(100000);
while (buffer.Length > 0);
{
uffer = await inputStream.ReadAsync(
buffer,
buffer.Capacity,
InputStreamOptions.Partial);
//
// Some stuff here...
totalBytesRead += buffer.Length;
Debug.WriteLine(buffer.Length + " " + totalBytesRead);
}
Debug.WriteLine(totalBytesRead);
I hope you guys enjoy it.
I'm using HttpWebRequest to connect to my in-house built HTTP server. My problem is that it is a lot slower than connecting to the server via for instance PostMan (https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en), which is probably using the built-in functions in Chrome to request data.
The server is built using this example on MSDN (http://msdn.microsoft.com/en-us/library/dxkwh6zw.aspx) and uses a buffer size of 64. The request is a HTTP request with some data in the body.
When connecting via PostMan, the request is split into a bunch of chunks and BeginRecieve() is called multiple times, each time receiving 64B and taking about 2 milliseconds. Except the last one, which receives less than 64B.
But when connecting with my client using HttpWebRequest, the first BeginRecieve() callback receives 64B and takes about 1 ms, the following receives only 47B and takes almost 200 ms, and finally the third receives about 58B and takes 2ms.
What is up with the second BeginRecieve? I note that the connection is established as soon as I start to write data to the HttpWebRequest input stream, but the data reception does not start until I call GetResponse().
Here is my HttpWebRequest code:
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;
if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
var dataBytes = Encoding.UTF8.GetBytes(data);
try
{
var dataStream = request.GetRequestStream();
dataStream.Write(dataBytes, 0, dataBytes.Length);
dataStream.Close();
}
catch (Exception ex)
{
throw;
}
}
WebResponse response = null;
try
{
response = request.GetResponse();
}
catch (Exception ex)
{
throw;
}
var responseReader = new StreamReader(rStream, Encoding.UTF8);
var responseStr = responseReader.ReadToEnd();
responseReader.Close();
response.Close();
What am I doing wrong? Why is it behaving so much differently than a HTTP request from a web browser? This is effectively adding 200ms of lag to my application.
This looks like a typical case of the Nagle algorithm clashing with TCP Delayed Acknowledgement. In your case you are sending a small Http Request (~170 bytes according to your numbers). This is likely less than the MSS (Maximum Segment Size) meaning that the Nagle Algorithm will kick in. The server is probably delaying the ACK resulting in a delay of up to 500 ms. See links for details.
You can disable Nagle via ServicePointManager.UseNagleAlgorithm = false (before issuing the first request), see MSDN.
Also see Nagle’s Algorithm is Not Friendly towards Small Requests for a detailed discussion including a Wireshark analysis.
Note: In your answer you are running into the same situation when you do write-write-read. When you switch to write-read you overcome this problem. However I do not believe you can instruct the HttpWebRequest (or HttpClient for that matter) to send small requests as a single TCP write operation. That would probably be a good optimization in some cases. Althought it may lead to some additional array copying, affecting performance negatively.
200ms is the typical latency of the Nagle algorithm. This gives rise to the suspicion that the server or the client is using Nagling. You say you are using a sample from MSDN as the server... Well there you go. Use a proper server or disable Nagling.
Assuming that the built-in HttpWebRequest class has an unnecessary 200ms latency is very unlikely. Look elsewhere. Look at your code to find the problem.
It seems like HttpWebRequest is just really slow.
Funny thing: I implemented my own HTTP client using Sockets, and I found a clue to why HttpWebRequest is so slow. If I encoded my ASCII headers into its own byte array and sent them on the stream, followed by the byte array encoded from my data, my Sockets-based HTTP client behaved exactly like HttpWebRequest: first it fills one buffer with data (part of the header), then it uses another buffer partially (the rest of the header), waits 200 ms and then sends the rest of the data.
The code:
TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();
// Send this out
stream.Write(headerData, 0, headerData.Length);
stream.Write(bodyData, 0, bodyData.Length);
stream.Flush();
The solution was of course to append the two byte arrays before sending them out on the stream. My application is now behaving as espected.
The code with a single stream write:
TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();
var totalData = new byte[headerBytes.Length + bodyData.Length];
Array.Copy(headerBytes,totalData,headerBytes.Length);
Array.Copy(bodyData,0,totalData,headerBytes.Length,bodyData.Length);
// Send this out
stream.Write(totalData, 0, totalData.Length);
stream.Flush();
And HttpWebRequest seems to send the header before I write to the request stream, so it might be implemented somewhat like my first code sample. Does this make sense at all?
Hope this is helpful for anyone with the same problem!
Try this: you need to dispose of your IDisposables:
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;
if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
var dataBytes = Encoding.UTF8.GetBytes(data);
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(dataBytes, 0, dataBytes.Length);
}
}
string responseStr;
using (var response = request.GetResponse())
{
using (var responseReader = new StreamReader(rStream, Encoding.UTF8))
{
responseStr = responseReader.ReadToEnd();
}
}
My method looks like this:
public string Request(string action, NameValueCollection parameters, uint? timeoutInSeconds = null)
{
parameters = parameters ?? new NameValueCollection();
ProvideCredentialsFor(ref parameters);
var data = parameters.ToUrlParams(); // my extension method converts the collection to a string, works well
byte[] dataStream = Encoding.UTF8.GetBytes(data);
string request = ServiceUrl + action;
var webRequest = (HttpWebRequest)WebRequest.Create(request);
webRequest.AllowAutoRedirect = false;
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = dataStream.Length;
webRequest.Timeout = (int)(timeoutInSeconds == null ? DefaultTimeoutMs : timeoutInSeconds * 1000);
webRequest.Proxy = null; // should make it faster...
using (var newStream = webRequest.GetRequestStream())
{
newStream.Write(dataStream, 0, dataStream.Length);
}
var webResponse = (HttpWebResponse)webRequest.GetResponse();
string uri = webResponse.Headers["Location"];
string result;
using (var sr = new StreamReader(webResponse.GetResponseStream()))
{
result = sr.ReadToEnd();
}
return result;
}
The server sends JSON in response. It works fine for small JSON, but when I request a large one - something goes wrong. By large one I mean something that takes 1-2 minutes to appear in a browser (google chrome, including server side generation time). It's actually 412KB of text. When I try to ask for the same JSON with the method above I get a web exception (timeout). I changed the timeout to 10 minutes (at least 5 times longer than chrome). Still the same.
Any ideas?
EDIT
This seems to have something to do with MS technologies. On IE this JSON also won't load.
Make sure you close your request. Otherwise, once you hit the maximum number of allowed connections (as low as four, for me, on one occasion), you have to wait for the earlier ones to time out. This is best done using
using (var response = webRequest.GetResponse()) {...