I have a class that I created to consume a REST API. I wrote the class to communicate asynchronously with the web service, since I didn't originally think I needed to have anything run synchronized. Now I am experiencing a situation where I realized using an asynchronous method is not ideal for one particular situation in my application since it runs out of order and causes exceptions since the application is attempting to call a method that it is not ready for. I'm not 100% sure why this is happening, but I think it's due to these methods being called in async void events within my UI. Here are some code snippets that show an example of the situation:
class MyForm : Form
{
private RestConnection connection;
private async void MyForm_Load(object sender, EventArgs e)
{
if(connection == null)
using (LogOnDialog logOnDialog = new LogOnDialog())
{
var result = logOnDialog.ShowDialog(this);
if(result == DialogResult.OK)
{
connection = logOnDialog.Connection;
}
}
formComboBox.DataSource = await connection.GetChoices();
}
}
class LogOnDialog : Form
{
public RestConnection Connection {private set;get;}
private async void saveButton_Click(object sender, EventArgs e)
{
RestConnection conn = new RestConnection(userNameTB.Text, passwordTb.Text);
await conn.LogIn();
if(conn.LoggedIn) //issue here
{
Connection = conn;
DialogResult = DialogResult.OK;
this.Close();
}
else
{
Connection = null;
DialogResult = DialogResult.Abort;
MessageBox.Show("Invalid Credentials, Try Again.");
}
}
}
What's happening is that the application is attempting to call connection.GetOptions(), but connection is still null because the LogOnDialog's async event that creates the connection and check's for a successful login before allowing the connection to be offered to the caller. However, since connection is null since the Click event hasn't completed a NullReferenceException is called. Additionally, if I continue past and ignore the exception an ObjectDisposedException is thrown since we're now outside of the using block.
I attempted to force the logon to be synchronous by removing the async keyword from the event, and calling Wait() on the login Method. This caused a deadlock. I also tried to capture the task using the below code, and spinwait for it:
Task t = conn.LogOn();
while(!t.IsCompleted)
Thread.Sleep(50);
This didn't deadlock, but it did spin forever. Every time I checked the breakpoint on the While condition the Task's status was always WAITINGFORACTIVATION and essentially locked up the application. In order to get this working I'm going to create some synchronous methods for this situation, but what would allow this to work properly and be async all the way?
EDIT: Additional Code Snippet as Requested for LogOn() and GetOptions()
class RestConnection
{
private string user;
private string password
private XDocument convertToXDoc(string functionName, IDictionary<string,string> parameters) {} //not shown, but this just creates an XML document in the required format for the REST service to consume.
private async Task<XDocument> SendCommand(XDocument commandDocument)
{
XDocument responseData = null;
byte[] data = Encoding.UTF8.GetBytes(commandDoc.ToString());
HttpWebRequest request = WebRequest.CreateHttp(this.serverUrl);
request.Method = "POST";
request.ContentType = "text/xml";
request.ContentLength = data.Length;
using (var requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(data, 0, data.Length);
}
HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;
using (var responseStream = response.GetResponseStream())
{
responseData = XDocument.Load(responseStream);
}
return responseData;
}
public async Task LogIn()
{
var parameters = new Dictionary<string, string>();
parameters.Add("USERNAME", userName);
parameters.Add("PASSWORD", passWord);
parameters.Add("CORELICTYPE", String.Empty);
parameters.Add("REMOTEAUTH", "False");
var xmlCommand = ConvertMethodToXml("LoginUserEx3", parameters);
var response = await SendCommand(xmlCommand);
//read response
switch (response.Root.Element("RESULTS").Element("RESULTVAL").Value)
{
case "0":
sessionId = response.Root.Element("SESSIONID").Value;
pingRequired = response.Root.Element("PINGTIME").Value != "0";
if (pingRequired)
{
pingInterval = int.Parse(response.Root.Element("PINGTIME").Value);
pingTimer = new Timer(pingInterval);
pingTimer.Elapsed += PingServerRequired;
pingTimer.Start();
}
loggedIn = true;
break;
//removed other cases for example since they all throw exceptions
default:
loggedIn = false;
throw new ConnectionException("Error");
}
}
}
The GetOptions() in the same format as the LogIn() method, except it returns a Task<List<Options>> from parsing the returned XDocument.
The problem is here:
{
Connection = null;
DialogResult = DialogResult.Abort; //<<------ this
MessageBox.Show("Invalid Credentials, Try Again.");
}
Assigning DialogResult will automatically close your form with the result you pass in. Remove that line and you will be fine (especially if you want the dialog to never close).
Related
I'm trying to use System.Net.Http.HttpClient to return the results of a call to a webpage, so that it implements a POST request. I don't really want to perform this asynchronously. My requirement is to wait until all the data is returned before continuing, so ideally I want synchronous method. However, sadly, it is not possible to just use HttpClient that way.
I've declared the following method, which is asynchronous, which takes a URL and key-value pairs to populate $_POST in the PHP:
private async Task<string> PostRequest(string cUrl, params string[] aParams)
{
HttpClient oClient;
Dictionary<string, string> oArgs;
int iA;
FormUrlEncodedContent oContent;
HttpResponseMessage oResponse;
// check we have an event number of parameters
if ((aParams.GetUpperBound(0) % 2) != 1) throw new Exception(
"Non-even number of parameters passed. Parameters are key-value pairs.");
// put the parameters into a dictionary
oArgs = new Dictionary<string, string>();
for (iA = 0; iA < aParams.GetUpperBound(0); iA += 2)
oArgs.Add(aParams[iA], aParams[iA + 1]);
oClient = new HttpClient();
oContent = new FormUrlEncodedContent(oArgs);
oClient.Timeout = new TimeSpan(0, 0, 10);
oResponse = await oClient.PostAsync(cUrl, oContent);
return await oResponse.Content.ReadAsStringAsync();
}
Now, annoyingly this has to be an asynchonous method. Ho hum. Ideally, I'd like to call it thus:
private void button2_Click(object sender, EventArgs e)
{
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
}
But I have the compile time error:
The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
What I'm doing above is (obviously) a test. In reality the button that kicks this off is a "Next >" in a wizard. It will use the results to populate a structure with data that other code in the wizard then accesses. I don't the above to occur asynchronously as I don't want other code touching that structure until it is populated.
My question is, how can I wrap a call to PostRequest so that I can wait for all the results to come in (some sort of ...while still processing...wait... loop) and then just return the results of the call, and use that without having to bubble async declarations up through my code?
As a second question, if I have to declare my cmdNext_Click as async, what happens if the user clicks it twice? I specifically want the UI thread to stop until the data is returned and processed.
Edit:
I've tried creating a wrapper function (which is non-async) thus:
private bool PostRequest2(string cUrl, ref string cResponse, params string[] aParams)
{
// This posts a request to the URL, using the parameters passed in aArgs. The response is returned in cResponse.
// cUrl - the URL to POST to
// cResponse - the response returned
// aParams - an even number of parameters, which are key-value pairs. The first of each pair is the name of the item. The second is its value.
int iWaitCount;
try
{
var response = PostRequest(cUrl, aParams);
Console.WriteLine(response);
iWaitCount = 0;
while (!response.IsCompleted)
{
Console.WriteLine("iWaitCount = " + iWaitCount.ToString());
Console.WriteLine("Status = " + response.Status.ToString());
response.Wait(500);
iWaitCount++;
}
cResponse = response.Result;
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This compiles correctly, but sits in the wait loop indefinitely with response.Status = WaitingForActivation.
There has to be a way to wrap an asynchronouns function in a synchrnous one. The alternative is to have to change all the return types (which are mostly bool - true on success) to Task, which I cannot then use in conditional statements - I have to await them instead. I've realised that this is the fundimental question, and this is a duplicate of: How to call asynchronous method from synchronous method in C#? which refers to await being a zombie virus that infects your code; this appears to be the case.
You can make your button void async (I would maybe return Task instead of void though)
await should mean that your method waits for the PostAsync call to complete.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await
The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes.
So this is essentially a synchronous call.
Now if you really don't want that void to be async, here's what I can remember off the top of my head:
In .NET 5+, you can use HttpClient.Send which is synchronous. (takes HttpRequestMessage)
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.send?view=net-5.0
Otherwise, you would need to do a .Result if you wanted to get the response. This type of consumption of async methods has been frowned upon in my experience.
Disable button2 until the operation is completed and use async inside the button2 click event.
change button2_Click to :
private async void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
button2.Enabled = true;
}
After much reading, and thank you to those above, I've got a working method now. It's nor perfect, but it works in this scenario where I'm calling one async method at a time, and wait processing to stop until it returns.
PostRequest above works correctly, but it must be declared async and called with await. Within my app, I have a variety of callers of it, which must also be declared async and use await when they call it. An example is:
private async Task<bool> ReadProductPrice()
{
string cCsv = "";
try
{
cProductCode = scSubscriptionType.GetSelectedKey().ToString();
var oResponse = await PostRequest("http://mywebsite.com/mywebpage.php",
"MagicToken", "12345",
"Query", "GetProductPrice",
"ProductCode", cProductCode);
if (oResponse == null) throw new Exception("Could not acquire product price from server. (1)");
cCsv = oResponse.ToString();
moProductPrice = new Dataset(_g);
if (!moProductPrice.ReadFromCsv(cCsv)) throw new Exception("Could not decode server response.");
if (moProductPrice.RecordCount != 1) throw new Exception("Could not acquire product price from server. (2)");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This works correctly and populates moProductPrice with the data returned from PostRequest. However, it is async.
I've create a wrapper function thus:
private bool ReadProductPrice2()
{
Task<bool> oTask;
frmWaitForTaskCompletion frm;
try
{
oTask = ReadProductPrice();
frm = new frmWaitForTaskCompletion();
frm.WaitForTaskCompletion(oTask, "Waiting for product price from server...");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This passes the Task<bool> returned from ReadProductPrice through to a form. The form contains a Label and a Timer, named lblMessage and tmr, containing the following code:
public partial class frmWaitForTaskCompletion : Form
{
private Task _task;
public frmWaitForTaskCompletion()
{
InitializeComponent();
}
public void WaitForTaskCompletion<TResult>(Task<TResult> oTask, string cMessage)
{
_task = oTask;
lblMessage.Text = cMessage;
this.ShowDialog();
return;
}
private void frmWaitForTaskCompletion_Load(object sender, EventArgs e)
{
tmr.Enabled = true;
}
private void tmr_Tick(object sender, EventArgs e)
{
if (_task.Status == TaskStatus.RanToCompletion)
this.Close();
}
}
The timer is set to an Interval of 1000 so that it shows for enough time for the user to recognise that a popup has occurred and to scan the message.
Ideally, I would like to replace the call to the wait form with this:
while (oTask.Status != TaskStatus.RanToCompletion) Thread.Sleep(100);
And I don't actually understand why this doesn't now work, but recognise that it doesn't; code never continues after this point, despite the fact that the wait form is effectively performing the same check.
In this way, I'm able to stop the await/async propogating up my call stack indefinitely; IMO should be the compiler's job to sort that out, not mine, and it signifcantly breaks the concept of encapsulation. I dislike the fact that I need to show a wait form for a short while, but in this context the user should be aware of the communication that is going on, so it's an ok solution.
You can try doing something like this
private void button2_Click(object sender, EventArgs e)
{
PostRequest(
"http://mywebsite.com/mypage.php",
"MagicToken",
"12345",
"Method",
"GetSomeData"
)
.ContinueWith(async request => {
var cResult = await request;
txt.Text = cResult.ToString();
})
.Wait();
}
The problem I have it is:
I tried to download 1000+ images -> it works, but it takes a very long time to load the image downloaded completely, and the program continues and downloads the next image etc... Until let's admit 100 but the 8th image is still not finished downloading.
So I would like to understand why I encounter such a problem here and / or how to fix this problem.
Hope to see an issue
private string DownloadSourceCode(string url)
{
string sourceCode = "";
try
{
using (WebClient WC = new WebClient())
{
WC.Encoding = Encoding.UTF8;
WC.Headers.Add("Accept", "image / webp, */*");
WC.Headers.Add("Accept-Language", "fr, fr - FR");
WC.Headers.Add("Cache-Control", "max-age=1");
WC.Headers.Add("DNT", "1");
WC.Headers.Add("Origin", url);
WC.Headers.Add("TE", "Trailers");
WC.Headers.Add("user-agent", Fichier.LoadUserAgent());
sourceCode = WC.DownloadString(url);
}
}
catch (WebException e)
{
if (e.Status == WebExceptionStatus.ProtocolError)
{
string status = string.Format("{0}", ((HttpWebResponse)e.Response).StatusCode);
LabelID.TextInvoke(string.Format("{0} {1} {2} ", status,
((HttpWebResponse)e.Response).StatusDescription,
((HttpWebResponse)e.Response).Server));
}
}
catch (NotSupportedException a)
{
MessageBox.Show(a.Message);
}
return sourceCode;
}
private void DownloadImage(string URL, string filePath)
{
try
{
using (WebClient WC = new WebClient())
{
WC.Encoding = Encoding.UTF8;
WC.Headers.Add("Accept", "image / webp, */*");
WC.Headers.Add("Accept-Language", "fr, fr - FR");
WC.Headers.Add("Cache-Control", "max-age=1");
WC.Headers.Add("DNT", "1");
WC.Headers.Add("Origin", "https://myprivatesite.fr//" + STARTNBR.ToString());
WC.Headers.Add("user-agent", Fichier.LoadUserAgent());
WC.DownloadFile(URL, filePath);
NBRIMAGESDWLD++;
}
STARTNBR = CheckBoxBack.Checked ? --STARTNBR : ++STARTNBR;
}
catch (IOException)
{
LabelID.TextInvoke("Accès non autorisé au fichier");
}
catch (WebException e)
{
if (e.Status == WebExceptionStatus.ProtocolError)
{
LabelID.TextInvoke(string.Format("{0} / {1} / {2} ", ((HttpWebResponse)e.Response).StatusCode,
((HttpWebResponse)e.Response).StatusDescription,
((HttpWebResponse)e.Response).Server));
}
}
catch (NotSupportedException a)
{
MessageBox.Show(a.Message);
}
}
private void DownloadImages()
{
const string URL = "https://myprivatesite.fr/";
string imageIDURL = string.Concat(URL, STARTNBR);
string sourceCode = DownloadSourceCode(imageIDURL);
if (sourceCode != string.Empty)
{
string imageNameURL = Fichier.GetURLImage(sourceCode);
if (imageNameURL != string.Empty)
{
string imagePath = PATHIMAGES + STARTNBR + ".png";
LabelID.TextInvoke(STARTNBR.ToString());
LabelImageURL.TextInvoke(imageNameURL + "\r");
DownloadImage(imageNameURL, imagePath);
Extension.SaveOptions(STARTNBR, CheckBoxBack.Checked);
}
}
STARTNBR = CheckBoxBack.Checked ? --STARTNBR : ++STARTNBR;
}
// END FUNCTIONS
private void BoutonStartPause_Click(object sender, EventArgs e)
{
if (Fichier.RGBIMAGES != null)
{
if (boutonStartPause.Text == "Start")
{
boutonStartPause.ForeColor = Color.DarkRed;
boutonStartPause.Text = "Pause";
if (myTimer == null)
myTimer = new System.Threading.Timer(_ => new Task(DownloadImages).Start(), null, 0, Trackbar.Value);
}
else if (boutonStartPause.Text == "Pause")
EndTimer();
Extension.SaveOptions(STARTNBR, CheckBoxBack.Checked);
}
}
So I would like to understand why I encounter such a problem here and / or how to fix this problem.
There are probably two reasons I can think of.
Connection/Port Exhaustion
Thread Pool Exhaustion
Connection/Port Exhaustion
This happens when you're attempting to create too many connections at once, or when the connections you made previously have not yet been released. When you use a WebClient the resources it uses sometimes don't get released immediately. This causes a delay between when that object is disposed and the actual time that the next WebClient attempting to use the same port/connection actually gets access to that port.
An example of something that would most likely cause Connection/Port Exhaustion
int i = 1_000;
while(i --> 0)
{
using var Client = new WebClient();
// do some webclient stuff
}
When you create a lot of web clients, which is sometimes necessary due to the inherent lack of concurrency in WebClient. There's a possibility that by the time the next WebClient is instantiated, the port that the last one was using may not be available yet, causing either a delay(while it waits for the port) or worse the next WebClient opening another port/connection. This can cause a never ending list of connections to open causing things to grind to a halt!
Thread Pool Exhaustion
This is caused by trying to create too many Task or Thread objects at once that block their own execution(via Thread.Sleep or a long running operation).
Normally this isn't an issue since the built in TaskScheduler does a really good job of keeping track of a lot of tasks and makes sure that they all get turns to execute their code.
Where this becomes a problem is the TaskScheduler has no context for which tasks are important, or which tasks are going to need more time than others to complete. So therefor when many tasks are processing long running operations, blocking, or throwing exceptions, the TaskScheduler has to wait for those tasks to finish before it can start new ones. If you are particularly unlucky the TaskScheduler can start a bunch of tasks that are all blocking and no tasks can start, even if all the other tasks waiting are small and would complete instantly.
You should generally use as few tasks as possible to increase reliability and avoid thread pool exhaustion.
What you can do
You have a few options to help improve the reliability and performance of this code.
Consider using HttpClient instead. I understand you may be required to use WebClient so I have provided answers using WebClient exclusively.
Consider Requesting multiple downloads/strings within the same task to avoid Thread Pool Exhaustion
Consider using a WebClient helper class that limits the available webclients that can be active at once, and has the ability to keep webclients open if you're going to be accessing the same website multiple times.
WebClient Helper Class
I created a very simple helper class to get you started. This will allow you to create WebClient requests asynchronously without having to worry about creating too many clients at once. The default limit is the number of Cores in the client's processor(this was chosen arbitrarily).
public class ConcurrentWebClient
{
// limits the number of maximum clients able to be opened at once
public static int MaxConcurrentDownloads => Environment.ProcessorCount;
// holds any clients that should be kept open
private static readonly ConcurrentDictionary<string, WebClient> Clients;
// prevents more than the alloted webclients to be open at once
public static readonly SemaphoreSlim Locker;
// allows cancellation of clients
private static CancellationTokenSource TokenSource = new();
static ConcurrentWebClient()
{
Clients = new ConcurrentDictionary<string, WebClient>();
Locker ??= new SemaphoreSlim(MaxConcurrentDownloads, MaxConcurrentDownloads);
}
// creates new clients, or if a name is provided retrieves it from the dictionary so we don't need to create more than we need
private async Task<WebClient> CreateClient(string Name, bool persistent, CancellationToken token)
{
// try to retrieve it from the dictionary before creating a new one
if (Clients.ContainsKey(Name))
{
return Clients[Name];
}
WebClient newClient = new();
if (persistent)
{
// try to add the client to the dict so we can reference it later
while (Clients.TryAdd(Name, newClient) is false)
{
token.ThrowIfCancellationRequested();
// allow other tasks to do work while we wait to add the new client
await Task.Delay(1, token);
}
}
return newClient;
}
// allows sending basic dynamic requests without having to create webclients outside of this class
public async Task<T> NewRequest<T>(Func<WebClient, T> Expression, int? MaxTimeout = null, string Id = null)
{
// make sure we dont have more than the maximum clients open at one time
// 100s was chosen becuase WebClient has a default timeout of 100s
await Locker.WaitAsync(MaxTimeout ?? 100_000, TokenSource.Token);
bool persistent = true;
if (Id is null)
{
persistent = false;
Id = string.Empty;
}
try
{
WebClient client = await CreateClient(Id, persistent, TokenSource.Token);
// run the expression to get the result
T result = await Task.Run<T>(() => Expression(client), TokenSource.Token);
if (persistent is false)
{
// just in case the user disposes of the client or sets it to ull in the expression we should not assume it's not null at this point
client?.Dispose();
}
return result;
}
finally
{
// make sure even if we encounter an error we still
// release the lock
Locker.Release();
}
}
// allows assigning the headers without having to do it for every webclient manually
public static void AssignDefaultHeaders(WebClient client)
{
client.Encoding = System.Text.Encoding.UTF8;
client.Headers.Add("Accept", "image / webp, */*");
client.Headers.Add("Accept-Language", "fr, fr - FR");
client.Headers.Add("Cache-Control", "max-age=1");
client.Headers.Add("DNT", "1");
// i have no clue what Fichier is so this was not tested
client.Headers.Add("user-agent", Fichier.LoadUserAgent());
}
// cancels a webclient by name, whether its being used or not
public async Task Cancel(string Name)
{
// look to see if we can find the client
if (Clients.ContainsKey(Name))
{
// get a token incase we have to emergency cance
CancellationToken token = TokenSource.Token;
// try to get the client from the dictionary
WebClient foundClient = null;
while (Clients.TryGetValue(Name, out foundClient) is false)
{
token.ThrowIfCancellationRequested();
// allow other tasks to perform work while we wait to get the value from the dictionary
await Task.Delay(1, token);
}
// if we found the client we should cancel and dispose of it so it's resources gets freed
if (foundClient != null)
{
foundClient?.CancelAsync();
foundClient?.Dispose();
}
}
}
// the emergency stop button
public void ForceCancelAll()
{
// this will throw lots of OperationCancelledException, be prepared to catch them, they're fast.
TokenSource?.Cancel();
TokenSource?.Dispose();
TokenSource = new();
foreach (var item in Clients)
{
item.Value?.CancelAsync();
item.Value?.Dispose();
}
Clients.Clear();
}
}
Request Multiple Things at Once
Here all I did was switch to using the helper class, and made it so you can request multiple things using the same connection
public async Task<string[]> DownloadSourceCode(string[] urls)
{
var downloader = new ConcurrentWebClient();
return await downloader.NewRequest<string[]>((WebClient client) =>
{
ConcurrentWebClient.AssignDefaultHeaders(client);
client.Headers.Add("TE", "Trailers");
string[] result = new string[urls.Length];
for (int i = 0; i < urls.Length; i++)
{
string url = urls[i];
client.Headers.Remove("Origin");
client.Headers.Add("Origin", url);
result[i] = client.DownloadString(url);
}
return result;
});
}
private async Task<bool> DownloadImage(string[] URLs, string[] filePaths)
{
var downloader = new ConcurrentWebClient();
bool downloadsSucessful = await downloader.NewRequest<bool>((WebClient client) =>
{
ConcurrentWebClient.AssignDefaultHeaders(client);
int len = Math.Min(URLs.Length, filePaths.Length);
for (int i = 0; i < len; i++)
{
// side-note, this is assuming the websites you're visiting aren't mutating the headers
client.Headers.Remove("Origin");
client.Headers.Add("Origin", "https://myprivatesite.fr//" + STARTNBR.ToString());
client.DownloadFile(URLs[i], filePaths[i]);
NBRIMAGESDWLD++;
STARTNBR = CheckBoxBack.Checked ? --STARTNBR : ++STARTNBR;
}
return true;
});
return downloadsSucessful;
}
I've made a simple program that has to continuosly check for data based on API.
So far, what I've done is making a timer, then execute the GET procedures on timer event
private void TimerStatus_Tick(object sender, EventArgs e)
{
//stop timer
TimerStatus.Stop();
//get data
getCommand();
//restart timer
TimerStatus.Start();
}
void getCommand()
{
string url = "https://somewhere/getcommand?token=somekey¶m=";
string param = "0";
WebRequest request = WebRequest.Create(url + param ); ;
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";
request.Credentials = CredentialCache.DefaultCredentials;
try
{
WebResponse response = request.GetResponse();
bool connected = false;
if ((((HttpWebResponse)response).StatusDescription) == "OK")
connected = true;
//continue if connected
if (connected)
{
using (Stream dataStream = response.GetResponseStream())
{
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
//check output
Console.WriteLine("Respond from server : " + responseFromServer);
try
{
//parse data, store value
parseThenProcess(responseFromServer);
}
catch
{
//parsing data error
Console.WriteLine("exception error response");
}
}
}
// Close the response.
response.Close();
}
catch
{
Console.WriteLine("Get command failed");
}
}
This code works fine for me. However, when I try to add more command that has different API in the timer event, the winforms feels kinda laggy. Is it just error on my side that irrelevant with the API handling or do I need to make some improvement about how to handle the API?
private void TimerStatus_Tick(object sender, EventArgs e)
{
//stop timer
TimerStatus.Stop();
//get data
getCommand_A();
getCommand_B();
getParameter_C();
getParameter_D();
//restart timer
TimerStatus.Start();
}
Not using a windows timer? And I am not joking. You have various approaches:
Learn how to use async and the async web interfaces so you do not block the UI thread too long.
or
use a separate thread or tasks (no need for a timer , you can have a task that then schedules another task, i.e.).
What you do is running it all on the UI thread and that is really not needed. Especially because you do send that synchronous so the UI blocks while the request is executed .This is a problem solved for many years by the UI knowing of async methods.
I've looked at some of the answers for similar questions and can't seem to find something that is applicable to what I'm doing. I need to make a few synchronous requests using HttpWebRequest (some using each verb, GET/PUT/POST/DELETE) and can't seem to get it to work. The example below works great when I manually use a 'refresh' button that I have in the design (works for any verb specified), but when I uncomment the section in 'b_send_Click' it doesn't work. What I'm looking for is a wrapper method that will encapsulate the REST client (the way 'b_send_Click' does in this example) and then take some action when the call is complete (the commented section in 'b_send_Click' as an example). Any ideas? By the way, this works well as a wrapper for async REST calls but I can't get the sync working...
using Microsoft.Phone.Controls;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Windows;
namespace WP8Rest
{
public partial class MainPage : PhoneApplicationPage
{
// global variables
public static string url = "http://mywebsite.com/API/some_api";
public static string request_body = "";
public static HttpWebRequest client = null;
public static HttpWebResponse response = null;
public static string server_response = "";
public static bool request_done = false;
public MainPage()
{
InitializeComponent();
}
private void b_send_Click(object sender, RoutedEventArgs e)
{
rest_request(sender, e);
/*
while (!request_done)
{
Thread.Sleep(100);
}
if (response != null)
{
l_status_code.Text = response.StatusCode.ToString();
l_status_description.Text = response.StatusDescription.ToString();
l_response.Text = server_response;
}
else
{
l_status_code.Text = "0";
l_status_description.Text = "Unable to complete request...";
l_response.Text = "Unable to complete request...";
}
*/
}
private void rest_request(object sender, RoutedEventArgs e)
{
request_done = false;
server_response = "";
request_body = tb_reqbody.Text;
client = (HttpWebRequest)WebRequest.Create(url);
client.Method = tb_verb.Text;
client.AllowAutoRedirect = true;
switch (tb_verb.Text)
{
case "GET":
client.BeginGetResponse(new AsyncCallback(GetResponseCallback), client);
break;
case "PUT":
client.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), client);
client.ContentType = "application/json";
break;
case "POST":
client.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), client);
client.ContentType = "application/json";
break;
case "DELETE":
client.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), client);
client.ContentType = "application/json";
break;
default:
MessageBox.Show("Use GET, PUT, POST, or DELETE.");
return;
}
l_response.Text = "Request sent...";
return;
}
private static void GetRequestStreamCallback(IAsyncResult async_result)
{
HttpWebRequest request = (HttpWebRequest)async_result.AsyncState;
Stream request_body_stream = request.EndGetRequestStream(async_result);
byte[] request_body_bytearray = Encoding.UTF8.GetBytes(request_body);
request_body_stream.Write(request_body_bytearray, 0, request_body.Length);
request_body_stream.Close();
request.BeginGetResponse(new AsyncCallback(GetResponseCallback), request);
}
private static void GetResponseCallback(IAsyncResult async_result)
{
HttpWebRequest request = (HttpWebRequest)async_result.AsyncState;
response = (HttpWebResponse)client.EndGetResponse(async_result);
Stream response_body_stream = response.GetResponseStream();
StreamReader stream_reader = new StreamReader(response_body_stream);
server_response = stream_reader.ReadToEnd();
response_body_stream .Close();
stream_reader.Close();
response.Close();
request_done = true;
}
private void b_refresh_Click(object sender, RoutedEventArgs e)
{
if (response != null)
{
l_response.Text = server_response;
l_status_code.Text = response.StatusCode.ToString();
l_status_description.Text = response.StatusDescription.ToString();
}
else
{
l_response.Text = "No response...";
}
}
}
}
Per #Industry86 I was able to get Microsoft.Net.Http installed. Changed code to:
private async void b_send_Click(object sender, RoutedEventArgs e)
{
l_response.Text = myMethod();
}
async Task<string> myMethod()
{
string address = "http://dev.getcube.com:65533/rest.svc/API/mirror";
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(address);
string responseText = await response.Content.ReadAsStringAsync();
return responseText;
}
Problem now is that it won't compile - "Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'string'. I changed the line in b_send_Click to l_response.Text = (myMethod()).Result; (not sure if this is correct or not) and when I click the 'b_send' button, it turns orange and the server never sees the request.
Creating a new answer to focus on your code changes.
put an await in front of your async method call:
private async void b_send_Click(object sender, RoutedEventArgs e)
{
l_response.Text = await myMethod();
}
when you use .Result, it halts the process until the result returns, thereby halting UI and everything else. Here is an SO answer detailing out the problems with this:
https://stackoverflow.com/a/13703845/311393
Quick lesson: with a void return value, an async method will "fire-and-forget" and never expect a return. With a Task or Task<T> return value, an async method called will halt the calling method until it's completed.
Thus b_send_Click will fire off a thread to do whatever. When it calls myMethod(), which is a Task, with the appropriate await keyword, it will stop in a synchronous fashion and wait till it's completed whatever it's doing.
And myMethod() has multiple async method calls but those have await's on them as well, so the thread will wait for those to complete synchronously as well.
Then it returns back to your text field and the UI, I assume in WP8, listens to changes to it's text field asynchronously.
Personally, I would use the HTTPClient that exists in Windows 8 (it's pretty awesome) and currently possibly still in beta for WP8 (https://nuget.org/packages/Microsoft.Net.Http) if not fully released already. The syntax is much smaller and simpler.
Or use RestSharp (http://restsharp.org/) but the async/await stuff isn't as robust. RestSharp is really good at serialization though.
that said, you also need to learn how Async operations occur. You are calling an Asynchronous operation and moving on without an "await". Therefore:
l_response.Text = server_response;
will not get set because, without the Thread.Sleep(100), the code will have fired off the async call and move on and server_response will still be null by the time it gets to that part.
if you want to want to wait for the return of that call, you need to use an "await" command and include the async signifier in the method declaration and return a Task (where object can be whatever you're intending to return). Example using HttpClient:
async Task<string> myMethod()
{
string address = "http://mywebsite.com/API/some_api";
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(address);
string responseText = await response.Content.ReadAsStringAsync();
return responseText;
}
and the resulting string "responseText" will have the JSON content to parse. Of course, if you're looking for a stream, its there too.
And you also have to remember that this method itself will require an await from the UI thread and the declaration will be required to be async:
private async void b_send_Click(object sender, RoutedEventArgs e)
{
someTextBox.Text = myMethod();
}
and event handlers should typically be the only async methods that return void.
Okay so basically I have a function that returns a string, but to get that string it uses webrequest which means while it's doing that webrequest the form is locking up unless I put it in a different thread.
But I can't figure out a way to capture the returned data in a thread since it's started using thread.start and that's a void.
Any help please?
Current code if it matters to anyone:
string CreateReqThread(string UrlReq)
{
System.Threading.Thread NewThread = new System.Threading.Thread(() => CreateReq(UrlReq));
string ReturnedData = "";
return ReturnedData;
}
string CreateReq(string url)
{
try
{
WebRequest SendReq = WebRequest.Create(url);
SendReq.Credentials = CredentialCache.DefaultCredentials;
SendReq.Proxy = WebRequest.DefaultWebProxy; //For closed port networks like colleges
SendReq.Proxy.Credentials = CredentialCache.DefaultCredentials;
SendReq.Timeout = 15000;
System.IO.StreamReader Reader = new System.IO.StreamReader(SendReq.GetResponse().GetResponseStream());
string Response = Reader.ReadToEnd();
Reader.Close();
return Response;
}
catch (WebException e)
{
EBox(e.Message, "Unknown Error While Connecting");
return null;
}
}
A common means of doing this is to use a Task<T> instead of a thread:
Task<string> CreateReqThread(string UrlReq)
{
return Task.Factory.StartNew() => CreateReq(UrlReq));
// In .NET 4.5, you can use (or better yet, reimplement using await/async directly)
// return Task.Run(() => CreateReq(UrlReq));
}
You can then call Task<T>.Result to get the returned value (later), when it's needed, or schedule a continuation on the task which will run when it completes.
This could look something like:
var request = CreateReqThread(theUri);
request.ContinueWith(t =>
{
// Shove results in a text box
this.textBox.Text = t.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
This also works perfectly with the new await/async support in C# 5.