Second call to HttpClient's PostAsJsonAsync stucks - c#

I'm trying to post some data to a web service and receive response with HttpClient in a console application. If I try to reuse a HttpClient instance, the first call to PostAsJsonAsync works as expected, but the second just waits forever. If i create a new HttpClient for every call then everything is OK.
public class DirectHandler
{
HttpClient httpClient;
public string SendToPlayer(object message)
{
if (httpClient == null)
{
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("..url...");
}
HttpResponseMessage response = httpClient.PostAsJsonAsync("", message).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsByteArrayAsync().Result;
return System.Text.Encoding.Default.GetString(result);
}
else
{
throw new HttpRequestException("Error code " + response.StatusCode + ", reason: " + response.ReasonPhrase);
}
}
}
The code using this class:
DirectHandler dh = new DirectHandler();
var resp = dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + resp);
Thread.Sleep(500);
resp = dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + resp);
The "server" is a HttpListener:
public class HTTPServer
{
void StartListener()
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add("my prefix");
listener.Start();
listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);
}
private void OnRequestReceive(IAsyncResult result)
{
HttpListener listener = (HttpListener)result.AsyncState;
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerResponse response = context.Response;
HttpListenerRequest request = context.Request;
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
Console.WriteLine(reader.ReadToEnd());
}
string responseString = "{'a': \"b\"}";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);
}
}
How can i reuse HttpClient?
Update:
I changed .Result to async/await, now the SendToPlayer method looks like this:
public async Task<string> SendToPlayer(object message)
{
if (httpClient == null)
{
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("..url...");
}
HttpResponseMessage response = await httpClient.PostAsJsonAsync("", message).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsByteArrayAsync();
return System.Text.Encoding.Default.GetString(result);
}
else
{
throw new HttpRequestException("Error code " + response.StatusCode + ", reason: " + response.ReasonPhrase);
}
}
It still waits forever in PostAsJsonAsync when called more than once.
Update2:
The test code, hangs on the second SendToPlayer line:
public static void Main(string[] args)
{
Task.Run(async () =>
{
DirectHandler dh = new DirectHandler();
var resp = await dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + str);
Thread.Sleep(500);
resp = await dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + str);
}).Wait();
}

I can't help but notice that the code you provided doesn't even compile. Like where does myObj in your main method come from? And how do you even start the http listener since the method StartListener is declared private?
These kind of issues make me suspect that the code you've posted is not the actual code that has the described problem.
Also, what do you put into myObj?
I copied and adjusted your code to make it work on my machine. Please take a look to see if it runs on your machine as well in a new console app (you'll need administrator permissions to run it)
public static void Main(string[] args)
{
var s = new HTTPServer();
s.StartListener();
Task.Run(async () =>
{
var myObj = 8;
DirectHandler dh = new DirectHandler();
var resp = await dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + resp);
Thread.Sleep(500);
resp = await dh.SendToPlayer(myObj);
Console.WriteLine("Received: " + resp);
}).Wait();
}
public class HTTPServer
{
public void StartListener()
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);
}
private void OnRequestReceive(IAsyncResult result)
{
HttpListener listener = (HttpListener)result.AsyncState;
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerResponse response = context.Response;
HttpListenerRequest request = context.Request;
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
Console.WriteLine(reader.ReadToEnd());
}
string responseString = "{'a': \"b\"}";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);
}
}
public class DirectHandler
{
HttpClient httpClient;
public async Task<string> SendToPlayer(object message)
{
if (httpClient == null)
{
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:8080");
}
HttpResponseMessage response = await httpClient.PostAsJsonAsync("", message).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsByteArrayAsync();
return System.Text.Encoding.Default.GetString(result);
}
else
{
throw new HttpRequestException("Error code " + response.StatusCode + ", reason: " + response.ReasonPhrase);
}
}
}
this outputs
8
Received: {'a': "b"}
8
Received: {'a': "b"}
Now, you might have simplified your code posted here. If there is any code you've not posted here it will be hard to solve.
Is your HttpListener running in a separate (console) app? Maybe the server process is terminated after the first call, due to an error for example.
If the above code works in a new console app try to find the differences with your code.

As I see it, there are two issues:
1) your server needs to wait around to process more than only one event, and
2) your client and server code should reside in separate execution threads.
Based on the way you've written it, the server will shut down shortly after starting up, which is why maybe the first request works but the second one hangs.
I've created an example:
https://github.com/paulsbruce/StackOverflowExamples/tree/master/SOF40934218
You have to kick the server project off first, which waits for incoming requests (StartListening doesn't hold the server thread open, so there is a while(IsRunning) { Thread.Sleep(100); } loop as an example.
https://github.com/paulsbruce/StackOverflowExamples/blob/master/SOF40934218/SOF40934218_Server/Server.cs
Then kick off the client as a separate process and watch it's console output. It does in fact get a second response (why there is a 5 second delay to allow you to see this).
https://github.com/paulsbruce/StackOverflowExamples/blob/master/SOF40934218/SOF40934218_Client/Client.cs
Hope these two suggestions help!

Related

HttpClient long polling interrupted by seperate thread

I'm running a messaging service bot, which utilities long polling to get user messages sent. This is all working fine, until I added a new component which sends a simple http get request to a heartbeat monitoring service every 30 seconds. After implementing this component, my bot code sends an additional poll request to the messaging service whenever the heartbeat component sends its request.
I'm assuming the request sent by the heartbeat component is interrupting the long polling, or something of that nature? If I comment out the heartbeat request, everything works fine.
I've tried using a shared HttpClient, and seperate HttpClients disposed after ever use.
Heartbeat monitor code:
using (var client = new HttpClient())
{
var response = await client.GetAsync(_heartbeatUrl);
var responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Log.LogMessage(response.ReasonPhrase, LogType.Error);
Log.LogMessage(responseString, LogType.Error);
}
Log.LogMessage(responseString, LogType.Verbose);
}
Message bot poll code:
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var jsonData = JsonConvert.SerializeObject(data);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
var fullString = _url + "/" + methodName;
Log.LogMessage("Querying: " + fullString, LogType.Verbose);
Log.LogMessage("With Data: " + jsonData, LogType.Verbose);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, fullString)
{
Content = content,
};
request.Headers.Authorization = new AuthenticationHeaderValue("mybot", _botToken);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
returnedObject = JsonConvert.DeserializeObject<T>(responseString);
if (!response.IsSuccessStatusCode)
{
Log.LogMessage(response.ReasonPhrase, LogType.Error);
Log.LogMessage(responseString, LogType.Error);
}
Log.LogMessage(responseString, LogType.Verbose);
}
Both of these are called in simple while(true) loops, i.e
Thread th = new Thread(async a =>
{
while (true)
{
await SendHeartbeat();
Thread.Sleep(30000);
}
});
and
new Thread(async () =>
{
while (true)
{
try
{
var results = await Methods.getUpdates(_service, update_id);
} catch (Exception e) { Log.LogMessage("Error with HTML query: " + e.Message, LogType.Error, e.StackTrace); }
}
}).Start();
I'm a bit stumped as to what could be causing this. Any suggestions are much appreciated!
Figured this out in the end. My calls to my static Log class had a lock on the file write method. I believe when the heartbeat ran and logged it's result, the polling code would be blocked when trying to Log stuff, causing the thread to be released by the async await. A new thread would start polling before the previous one had finished executing.

How to process multiple connections simultaneously with HttpListener?

In the application that I build, there is a need for webserver that can serve, simultaneously, multiple clients.
For that I use the HttpListener object. with its Async methods\events BeginGetContext and EndGetContext.
In the delegated method, there is a call for the listener to start listening again, and it works.. mostly.
The code provided is a mix of code that i found here and there, and a delay, to simulate a data processing bottleneck.
The problem is, it starts to manage the next connection only AFTER the last one was served.. no use for me.
public class HtServer {
public void startServer(){
HttpListener HL = new HttpListener();
HL.Prefixes.Add("http://127.0.0.1:800/");
HL.Start();
IAsyncResult HLC = HL.BeginGetContext(new AsyncCallback(clientConnection),HL);
}
public void clientConnection(IAsyncResult res){
HttpListener listener = (HttpListener)res.AsyncState;
HttpListenerContext context = listener.EndGetContext(res);
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
// add a delay to simulate data process
String before_wait = String.Format("{0}", DateTime.Now);
Thread.Sleep(4000);
String after_wait = String.Format("{0}", DateTime.Now);
string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
// You must close the output stream.
output.Write(buffer, 0, buffer.Length);
output.Close();
listener.BeginGetContext(new AsyncCallback(clientConnection), listener);
}
}
edit
private static void OnContext(IAsyncResult ar)
{
var ctx = _listener.EndGetContext(ar);
_listener.BeginGetContext(OnContext, null);
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " Handling request");
var buf = Encoding.ASCII.GetBytes("Hello world");
ctx.Response.ContentType = "text/plain";
// prevent thread from exiting.
Thread.Sleep(3000);
// moved these lines here.. to simulate process delay
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
ctx.Response.OutputStream.Close();
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " completed");
}
the output is
Well. That's because you start to fetch the next context after you have processed the first. Don't do that. Get the next context directly:
public void clientConnection(IAsyncResult res){
HttpListener listener = (HttpListener)res.AsyncState;
HttpListenerContext context = listener.EndGetContext(res);
//tell listener to get the next context directly.
listener.BeginGetContext(clientConnection, listener);
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
// add a delay to simulate data process
String before_wait = String.Format("{0}", DateTime.Now);
Thread.Sleep(4000);
String after_wait = String.Format("{0}", DateTime.Now);
string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
// You must close the output stream.
output.Write(buffer, 0, buffer.Length);
output.Close();
}
Here is my sample code that proves that it work (updated per request of the OP):
class Program
{
private static HttpListener _listener;
static void Main(string[] args)
{
_listener = new HttpListener();
_listener.Prefixes.Add("http://localhost/asynctest/");
_listener.Start();
_listener.BeginGetContext(OnContext, null);
Console.ReadLine();
}
private static void OnContext(IAsyncResult ar)
{
var ctx = _listener.EndGetContext(ar);
_listener.BeginGetContext(OnContext, null);
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " Handling request");
var buf = Encoding.ASCII.GetBytes("Hello world");
ctx.Response.ContentType = "text/plain";
// simulate work
Thread.Sleep(10000);
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
ctx.Response.OutputStream.Close();
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " completed");
}
}
Generates:
Both requests starts to get processed directly.
Why the above code works
HTTP have something called pipelining. It means that all requests that are received over the same connection must get their responses in the same order. However, the built in HttpListener doesn't seem to support pipelining, instead it's completes the response for the first request before taking care of the second. It's therefore important that you make sure that every request is sent over a new connection.
The easiest way to do that is to use different browsers when trying out the code. I did that, and as you see both my requests are handled at the same time.
Try this instead..
This will use asynchronous coding to ensure that there is no blocking. Blocking means when a thread, sleeps, which is typically how programs tend to "freeze". By using this code, you run non-blocking, which means that its almost impossible to "freeze" the application.
public async Task handleClientConnection(HttpListener listener){
HttpListenerContext context = await listener.GetContextAsync();
var ret = handleClientConnection(listener);
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
// add a delay to simulate data process
String before_wait = String.Format("{0}", DateTime.Now);
await Task.Wait(4000);
String after_wait = String.Format("{0}", DateTime.Now);
string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
using(System.IO.Stream output = response.OutputStream)
output.Write(buffer, 0, buffer.Length);
await ret;
}
public void startServer(){
HttpListener HL = new HttpListener();
HL.Prefixes.Add("http://127.0.0.1:800/");
HL.Start();
await handleClientConnection(HL);
}

Sending the server time as a response to HTTP GET via c# service

Alright, I think I have it working 100% now!
Here's the code, any critique is welcome, this was my first attempt at c#, coming from a mostly JS background.
Ended up using thread.abort, not sure if that is the best way to end this. I put in a _shouldStop bool as well.
public partial class TimeReporterService : ServiceBase
{
private Thread worker = null;
private bool _shouldStop = false;
public TimeReporterService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
_shouldStop = false;
worker = new Thread(SimpleListenerExample);
worker.Name = "Time Reporter";
worker.IsBackground = false;
worker.Start();
}
protected override void OnStop()
{
_shouldStop = true;
worker.Abort();
}
void SimpleListenerExample()
{
string[] prefixes = new[] { "http://*:12227/" };
// URI prefixes are required,
// for example "http://contoso.com:8080/index/".
if (prefixes == null || prefixes.Length == 0)
throw new ArgumentException("prefixes");
// Create a listener.
HttpListener listener = new HttpListener();
// Add the prefixes.
foreach (string s in prefixes)
{
listener.Prefixes.Add(s);
}
listener.Start();
while (!_shouldStop)
{
// Note: The GetContext method blocks while waiting for a request.
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
string responseString = "{\"systemtime\":\"" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\"}";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
listener.Stop();
}
}
Something you could use in the HTTPListener request method.
public void HttpListenerCallback(IAsyncResult result)
{
HttpListener listener = (HttpListener)result.AsyncState;
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerResponse Response = context.Response;
String dateAsString = DateTime.Now.ToString(#"MM\/dd\/yyyy h\:mm tt");
byte[] bOutput = System.Text.Encoding.UTF8.GetBytes(dateAsString);
Response.ContentType = "text/plain";
Response.ContentLength64 = bOutput.Length;
Stream OutputStream = Response.OutputStream;
OutputStream.Write(bOutput, 0, bOutput.Length);
OutputStream.Close();
}
For this you should use HTTPListener in asynchronous (non-blocking) mode.
Ex:
public void NonblockingListener()
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8081/");
listener.Start();
IAsyncResult result = listener.BeginGetContext(
new AsyncCallback(HttpListenerCallback), listener);
Console.WriteLine("Waiting for request to be processed asyncronously.");
result.AsyncWaitHandle.WaitOne(); //just needed to don't close this thread, you can do other work or run in a loop
Console.WriteLine("Request processed asyncronously.");
listener.Close();
}
More info: http://msdn.microsoft.com/en-us//library/system.net.httplistener.aspx

Premature end of data while reading from WebResponse class

I am developing a Windows Store App using C# and I am very new at this platform (I have been primarily working on IOS and Android).
I have a simple Async method to download raw data from a remote server. It works ok except that I keep seeing random incomplete reads from the WebResponse class. It is pretty simple method and I cant figure out why it would end prematurely. The remote server is working fine ( ios/web/android fine and are retrieving data) so I am obviously doing something wrong here.
Any help will be great in figuring out this problem.
public async Task<byte[]> doGETRequestAsync(String url)
{
callSuccess = false;
byte[] responseFromServer = null;
try
{
WebRequest request = WebRequest.Create(url);
request.Method = "GET";
WebResponse response = await request.GetResponseAsync();
using (Stream dataStream = response.GetResponseStream())
{
responseFromServer = new byte[response.ContentLength];
int readCount = await dataStream.ReadAsync(responseFromServer, 0, (int)response.ContentLength);
if (readCount != response.ContentLength)
throw new IOException("Premature end of data. Expected: " + response.ContentLength + " received: " + readCount);
}
response.Dispose();
}
catch (HttpRequestException hre)
{
Debug.WriteLine("Exception performing network call : " + hre.ToString());
}
catch (Exception e)
{
Debug.WriteLine("Exception performing network call : " + e.ToString());
}
return responseFromServer;
}
I switched to using HttpClient and HttpClientHandler and it works perfectly. This also supports storing cookies and reusing that on every call.
Here is the code that can handle both GET and POST and return the data as an array of bytes[]. If the response is a utf8 encoded string, then the bytes can be converted to string using System.Text.Encoding.UTF8.GetString(respBytes, 0, respBytes.Length);
Hope it is helpful
class Network
{
static CookieContainer cookieJar = new CookieContainer();
List<KeyValuePair<string, string>> postParameters = new List<KeyValuePair<string, string>>();
// Add post parameter before calling NetworkRequestAsync for POST calls.
public void addPostParameter(String key, String value)
{
postParameters.Add(new KeyValuePair<string, string>(key, value));
}
public async Task<byte[]> NetworkRequestAsync(String url, bool GET_REQUEST)
{
callSuccess = false;
byte[] respBytes = null;
try
{
HttpClientHandler handler = new HttpClientHandler()
{
// Use and reuse cookies in the cookiejar
CookieContainer = cookieJar
};
handler.AllowAutoRedirect = false;
handler.UseCookies = true;
HttpClient client = new HttpClient(handler as HttpMessageHandler)
{
BaseAddress = new Uri(#url)
};
HttpResponseMessage response = null;
if (GET_REQUEST)
{
response = await client.GetAsync(client.BaseAddress);
}
else
{
HttpContent content = new FormUrlEncodedContent(postParameters);
//String postparam=await content.ReadAsStringAsync();
//Debug.WriteLine("Post Param1=" + postparam);
response = await client.PostAsync(client.BaseAddress, content);
callSuccess = true;
}
respBytes = await response.Content.ReadAsByteArrayAsync();
}
catch (Exception e)
{
Debug.WriteLine("Exception performing network call : " + e.ToString());
}
return respBytes;
}
}

How to use HttpWebRequest (.NET) asynchronously?

How can I use HttpWebRequest (.NET, C#) asynchronously?
Use HttpWebRequest.BeginGetResponse()
HttpWebRequest webRequest;
void StartWebRequest()
{
webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}
void FinishWebRequest(IAsyncResult result)
{
webRequest.EndGetResponse(result);
}
The callback function is called when the asynchronous operation is complete. You need to at least call EndGetResponse() from this function.
By far the easiest way is by using TaskFactory.FromAsync from the TPL. It's literally a couple of lines of code when used in conjunction with the new async/await keywords:
var request = WebRequest.Create("http://www.stackoverflow.com");
var response = (HttpWebResponse) await Task.Factory
.FromAsync<WebResponse>(request.BeginGetResponse,
request.EndGetResponse,
null);
Debug.Assert(response.StatusCode == HttpStatusCode.OK);
If you can't use the C#5 compiler then the above can be accomplished using the Task.ContinueWith method:
Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
request.EndGetResponse,
null)
.ContinueWith(task =>
{
var response = (HttpWebResponse) task.Result;
Debug.Assert(response.StatusCode == HttpStatusCode.OK);
});
Considering the answer:
HttpWebRequest webRequest;
void StartWebRequest()
{
webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}
void FinishWebRequest(IAsyncResult result)
{
webRequest.EndGetResponse(result);
}
You could send the request pointer or any other object like this:
void StartWebRequest()
{
HttpWebRequest webRequest = ...;
webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), webRequest);
}
void FinishWebRequest(IAsyncResult result)
{
HttpWebResponse response = (result.AsyncState as HttpWebRequest).EndGetResponse(result) as HttpWebResponse;
}
Greetings
Everyone so far has been wrong, because BeginGetResponse() does some work on the current thread. From the documentation:
The BeginGetResponse method requires some synchronous setup tasks to
complete (DNS resolution, proxy detection, and TCP socket connection,
for example) before this method becomes asynchronous. As a result,
this method should never be called on a user interface (UI) thread
because it might take considerable time (up to several minutes
depending on network settings) to complete the initial synchronous
setup tasks before an exception for an error is thrown or the method
succeeds.
So to do this right:
void DoWithResponse(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
Action wrapperAction = () =>
{
request.BeginGetResponse(new AsyncCallback((iar) =>
{
var response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
responseAction(response);
}), request);
};
wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
{
var action = (Action)iar.AsyncState;
action.EndInvoke(iar);
}), wrapperAction);
}
You can then do what you need to with the response. For example:
HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
Console.Write(body);
});
public static async Task<byte[]> GetBytesAsync(string url) {
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
using (var content = new MemoryStream())
using (var responseStream = response.GetResponseStream()) {
await responseStream.CopyToAsync(content);
return content.ToArray();
}
}
public static async Task<string> GetStringAsync(string url) {
var bytes = await GetBytesAsync(url);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
I ended up using BackgroundWorker, it is definitely asynchronous unlike some of the above solutions, it handles returning to the GUI thread for you, and it is very easy to understand.
It is also very easy to handle exceptions, as they end up in the RunWorkerCompleted method, but make sure you read this: Unhandled exceptions in BackgroundWorker
I used WebClient but obviously you could use HttpWebRequest.GetResponse if you wanted.
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) => {
args.Result = new WebClient().DownloadString(settings.test_url);
};
worker.RunWorkerCompleted += (sender, e) => {
if (e.Error != null) {
connectivityLabel.Text = "Error: " + e.Error.Message;
} else {
connectivityLabel.Text = "Connectivity OK";
Log.d("result:" + e.Result);
}
};
connectivityLabel.Text = "Testing Connectivity";
worker.RunWorkerAsync();
.NET has changed since many of these answers were posted, and I'd like to provide a more up-to-date answer. Use an async method to start a Task that will run on a background thread:
private async Task<String> MakeRequestAsync(String url)
{
String responseText = await Task.Run(() =>
{
try
{
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
WebResponse response = request.GetResponse();
Stream responseStream = response.GetResponseStream();
return new StreamReader(responseStream).ReadToEnd();
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
}
return null;
});
return responseText;
}
To use the async method:
String response = await MakeRequestAsync("http://example.com/");
Update:
This solution does not work for UWP apps which use WebRequest.GetResponseAsync() instead of WebRequest.GetResponse(), and it does not call the Dispose() methods where appropriate. #dragansr has a good alternative solution that addresses these issues.
public void GetResponseAsync (HttpWebRequest request, Action<HttpWebResponse> gotResponse)
{
if (request != null) {
request.BeginGetRequestStream ((r) => {
try { // there's a try/catch here because execution path is different from invokation one, exception here may cause a crash
HttpWebResponse response = request.EndGetResponse (r);
if (gotResponse != null)
gotResponse (response);
} catch (Exception x) {
Console.WriteLine ("Unable to get response for '" + request.RequestUri + "' Err: " + x);
}
}, null);
}
}
Follow up to the #Isak 's answer, which is very good. Nonetheless it's biggest flaw is that it will only call the responseAction if the response has status 200-299. The best way to fix this is:
private void DoWithResponseAsync(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
Action wrapperAction = () =>
{
request.BeginGetResponse(new AsyncCallback((iar) =>
{
HttpWebResponse response;
try
{
response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
}
catch (WebException ex)
{
// It needs to be done like this in order to read responses with error status:
response = ex.Response as HttpWebResponse;
}
responseAction(response);
}), request);
};
wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
{
var action = (Action)iar.AsyncState;
action.EndInvoke(iar);
}), wrapperAction);
}
And then as #Isak follows:
HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
Console.Write(body);
});
I've been using this for async UWR, hopefully it helps someone
string uri = "http://some.place.online";
using (UnityWebRequest uwr = UnityWebRequest.Get(uri))
{
var asyncOp = uwr.SendWebRequest();
while (asyncOp.isDone == false) await Task.Delay(1000 / 30); // 30 hertz
if(uwr.result == UnityWebRequest.Result.Success) return uwr.downloadHandler.text;
Debug.LogError(uwr.error);
}

Categories

Resources