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.
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();
}
Please forgive me for any noobish mistakes seen below, I'm learning some of the concepts I'm attempting to work with.
Problem:
While debugging my app, I was able to call an async function with Task.Start(). I felt that the app was in a working state for the phase I'm in so removed all breakpoints with CTRL + SHIFT + F9.
Once I ran the app with no breakpoints it would fail due to a property not getting populated. Now when I try to debug any breakpoint I set in the async function that handles most of the work is longer hit. It's like it is getting skipped. Can anyone see a reason why GetWowAuctionFileInfo isn't being called?
GetWowAuctionFileInfo is what is not getting called, or at least appears to be not getting called.
Thanks.
Relevant Code
Caller Function
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
Called Functions
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
UPDATE
Thanks again all for the detailed commentary. I've gone through much documentation regarding Task usage and learned a lot in this exercise. I'm marking the answer from #Johnathon as the solution because it provided exactly what I was asking for and provided a very helpful link for more information.
Your GetWowAuctionFileInfo method is an asynchronous method, and you await an async call within it without returning a Task. In general it is bad practice to use async void. Instead, turn your GetWowAuctionFileInfo method into async Task<List<string>> GetWowAuctionFileInfo. This will let you await the GetAsync call, parse the data, and return the collection to the caller without having to use a ReturnObject.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
Because the method was originally async void, you could not await the calling of it in your buttonTestJSFCHI_Click. Now that we've made it all Task based, you can await it within your event handler. Note that event handlers are generally the only acceptable place to use async void. Any time you are responsible for the creation of the methods, and not constrained by a contract (like event handlers), you should always return a Task on your async methods.
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
The reason you saw the expected result while debugging is because the debug session was slow enough that the async operation completed in time for your code to use it. When running the app outside of the debugger, it runs faster than the async operation could complete, preventing you from seeing the data. Thus the need to await the entire async call stack, so you can prevent further execution from happening down that code-path until you receive all of the desired data.
Microsoft has a good write up on Task based programming, I'd take a read through it to help you understand it some.
EDIT
Just to clarify, when you return a Task<T> on your methods, you will be given the result when you await. For example:
List<string> result = await StartTask();
Even though StartTask returns Task<List<string>>, the await operation will wait for the StartTask() method to complete, and then unwrap the result from the Task<T> object and give you the result back automatically. So don't let the method signature fool you, if you await it, you will be given back the resulting data, and not the actual Task itself. There won't be any need for you to pull the data out of the Task manually.
Because you not waiting for result.
You loop with ReturnedData before it was assigned with data.
I think you don't need to create new Task at all. Make GetWowAuctionFileInfo method properly asynchronous which returns Task.
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
Change StartTask to return Task. Because we not awaiting result here we don't need make method asynchronous.
Suggest to change name of this method to LoadData for example, which give more information about what this method does.
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
Then you can call it in Button_Click event handler
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
I have a challenge, I need to call many http request and handle each of them.
How to do it, I don't want to wait for get response from one of them and then call next, how to assign a method for process response (like callback).
How can define callback and assign to each of them ?
What you need is an Asynchronous programming model where you create async tasks and later use await keyword for the response.
So essentially you are not waiting for the first async call to finish, you'd just fire as many async tasks as you wish and wait to get a response only when you need the response to move ahead with your program logic.
Have a look at below for more details:
https://msdn.microsoft.com/en-us/library/hh696703.aspx
1) you can call that normaly(noneasync):
public string TestNoneAsync()
{
var webClient = new WebClient();
return webClient.DownloadString("http://www.google.com");
}
2) you can use APM (async):
private void SpecAPI()
{
var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
//req.Method = "HEAD";
req.BeginGetResponse(
asyncResult =>
{
var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
var headersText = formatHeaders(resp.Headers);
Console.WriteLine(headersText);
}, null);
}
private string formatHeaders(WebHeaderCollection headers)
{
var headerString = headers.Keys.Cast<string>()
.Select(header => string.Format("{0}:{1}", header, headers[header]));
return string.Join(Environment.NewLine, headerString.ToArray());
}
3) you can create a callback and asign it,EAP.(async .net 2):
public void EAPAsync()
{
var webClient = new WebClient();
webClient.DownloadStringAsync(new Uri("http://www.google.com"));
webClient.DownloadStringCompleted += webClientDownloadStringCompleted;
}
void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
// use e.Result
Console.WriteLine("download completed callback.");
}
4) you can use newer way TAP, cleare way (c# 5). it's recommended:
public async Task<string> DownloadAsync(string url)
{
var webClient = new WebClient();
return await webClient.DownloadStringTaskAsync(url);
}
public void DownloadAsyncHandler()
{
//DownloadAsync("http://www.google.com");
}
threading in this solution is't good approch.(many threads that pending to call http request!)
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).
Im trying to make an async method that returns a value. everything work when use the method without return. you can process data , but the problem appears when the return clause added. the program freeze completely without any error or for a while.
please see the code:
public void runTheAsync(){
string resp = sendRequest("http://google.com","x=y").Result;
}
public async Task<string> sendRequest(string url, string postdata)
{
//There is no problem if you use void as the return value , the problem appears when Task<string> used. the program fully go to freeze.
Console.WriteLine("On the UI thread.");
string result = await TaskEx.Run(() =>
{
Console.WriteLine("Starting CPU-intensive work on background thread...");
string work = webRequest(url,postdata);
return work;
});
return result;
}
public string webRequest(string url, string postdata)
{
string _return = "";
WebClient client = new WebClient();
byte[] data = Encoding.UTF8.GetBytes(postdata);
Uri uri = new Uri(url);
_return = System.Text.Encoding.UTF8.GetString(client.UploadData(uri, "POST", data));
return _return;
}
string resp = sendRequest("http://google.com","x=y").Result;
That's your problem. If you call Result on a Task, it blocks until the Task finishes.
Instead, you can do this:
public async void runTheAsync()
{
string resp = await sendRequest("http://google.com","x=y");
}
But creating async void methods should be avoided. Whether you actually can avoid it, depends on how are you calling it.
Try this, data correctness checks etc. omitted but you ignored them either :-):
public async Task<string> UploadRequestAsync(string url, string postdata)
{
string result = await Encoding.GetString(
new WebClient().UploadData(new Uri(uri), "POST", Encoding.UTF8.GetBytes(postdata)));
return result;
}
You somehow doing the work twice, awaiting a explicitly started task. I'd be curious to see what the generated code for this looks like... And of course, in production code use the proper classes from .NET 4.5.