I am building an Android app through Xamarin/MonoTouch (so using C# rather than Java) and would like to display a ProgressBar while the app is communicating with the server. I have been attempting to do this using async, but I don't have a good understanding of how threading works, and I've been running into the same issue for the past few hours - the ProgressBar shows after my method call rather than before. My code is in the OnCreate() method, which I have also overridden to be async. Here it is:
loginButton.Click += async (sender, e) =>
{
progbar.Visibility = ViewStates.Visible;
var userFetcher = new UserFetcher();
var json = await userFetcher.FetchUserDetailsAsync(/*parameters*/);
//the above method is async and returns a Task<JsonValue>
ParseUserDetails(json); //another method I created
progbar.Visibility = ViewStates.Invisible;
//do some stuff using the parsed data
};
The issue I'm running into is that the FetchUserDetailsAsync seems to be blocking my thread. I have been testing this by shutting the server off so that it takes a long response time (but even when I test stuff like Thread.Sleep(5000) I have the same issue). After the method has been called, it runs both progbar.Visibility = ViewStates.Visible; and progbar.Visibility = ViewStates.Invisible; right after one another - I know this because when I comment out the Invisible part, the ProgressBar appears after my method got a "response" from the server. The compiler has also been giving me messages like "Skipped 67 frames! The application may be doing too much work on its main thread."
Like I said earlier, I'm not really experienced with threading, so it's very possible I'm just naively doing something wrong. Does anyone have a solution to this issue?
EDIT: Here is the source code for FetchUserDetailsAsync:
public async Task<JsonValue> FetchUserDetailsAsync(string url, string username, string password)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
request.ContentType = "application/json";
request.Method = "GET";
string myBasicHeader = AuthenticationHelper.MakeHeader(username, password);
request.Headers.Add("Authorization", "Basic " + myBasicHeader);
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
JsonValue jsonDoc = await Task.Run(() => JsonObject.Load(stream));
return jsonDoc;
}
}
}
catch (WebException e)
{
Console.WriteLine(e.ToString());
if (e.Status == WebExceptionStatus.ProtocolError)
{
var response = e.Response as HttpWebResponse;
if (response != null)
{
Console.WriteLine("HTTP Status Code: " + (int)response.StatusCode);
}
else
{
Console.WriteLine("No http status code available");
}
}
return null;
}
}
It looks like either FetchUserDetailsAsync or ParseUserDetails is blocking the UI thread. Because Task.Sleep(5000) is running synchronously. Try await Task.Delay(5000); in the line of FetchUserDetailsAsync to see if the progressbar show up. If it does, then you probably need to go into FetchUserDetailsAsync implementation to make sure it is implemented async
I figured out the issue. I was calling GetResponse() instead of GetResponseAsync() in my FetchUserDetailsAsync method.
Related
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.
So, I'm really a PHP developer, so no doubt this is a really obvious thing I'm missing.
I seem to be having problem with running async methods - I've tried a couple of ways (using await, using a Task), and I keep getting the above error whenever calling a method that is async..
The function as it stands currently is ...
public async static void deleteCommands(List<int> commandIds)
{
Tebex.logWarning("Async Delete....");
await Task.Run(() =>
{
String url = "http://www.example.com/" + "queue?";
String amp = "";
foreach (int CommandId in commandIds)
{
url = url + amp + "ids[]=" + CommandId;
amp = "&";
}
Tebex.logWarning("DELETE " + url);
var request = WebRequest.Create(url);
request.Method = "DELETE";
request.Headers.Add("APIKey", "myApiKey");
var response = (HttpWebResponse) request.GetResponse();
Tebex.logWarning(response.ToString());
}).ConfigureAwait(false);
}
and I'm calling it from another method (I don't need the response or anything, it's a fire-and-forget method)
try
{
deleteCommands(executedCommands);
executedCommands.Clear();
}
catch (Exception ex)
{
Tebex.logError(ex.ToString());
}
Previously I was using await request.getResponseAsync() but I received the same error -
public async static void deleteCommands(List<int> commandIds)
{
Tebex.logWarning("Async Delete....");
String url = "http://www.example.com/" + "queue?";
String amp = "";
foreach (int CommandId in commandIds)
{
url = url + amp + "ids[]=" + CommandId;
amp = "&";
}
Tebex.logWarning("DELETE " + url);
var request = WebRequest.Create(url);
request.Method = "DELETE";
request.Headers.Add("APIKey", "myApiKey");
await request.GetResponseAsync();
request = null;
}
As I mentioned, I'm probably missing something obvious, but I can't figure out what!
I'm not sure if any of this resolves your exception, but it should be changed regardless.
Instead of returning void you should return Task from deleteCommands. More on that here: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx?f=255&MSPPError=-2147217396
Depending on how your calling method looks, and how you'd like thread-management to function, there are a couple of solutions:
The preferred:
await deleteCommands(executedCommands);
This requires a async method though.
If you really want to use Task.Run (forces a new thread. Considder the load), then use it when calling the method instead:
Task.Run(async () => await deleteCommands(executedCommands));
Additionaly if you have an async method it, and all following calls, should also follow the async pattern. Switch to async WebRequests and use an async logger.
You should not clear the executedCommands list immediately because it is a fire and forget method (list can be cleared before the method finishes execution)
You should use ContinueWith instead
deleteCommands(executedCommands)
.ContinueWith(r => executedCommands.Clear());
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.
I have a problem, I want to wait in the Main() until the Download() is finished. However, the file downloading/checking starts, at the same time the other lines start executing.
How can I use awaitor anything else to wait in the Main?
private void Main()
{
Download("http://webserver/file.xml");
//Do something here ONLY if the file exists!!
}
//This method invokes the URL validation
private void Download(downloadURL)
{
System.Uri targetUri = new System.Uri(downloadURL);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(targetUri);
request.BeginGetResponse(new AsyncCallback(WebRequestCallBack), request);
}
//In this method the URL is being checked for its validity
void WebRequestCallBack(IAsyncResult result)
{
HttpWebRequest resultInfo = (HttpWebRequest)result.AsyncState;
HttpWebResponse response;
string statusCode;
try
{
response = (HttpWebResponse)resultInfo.EndGetResponse(result);
statusCode = response.StatusCode.ToString();
}
catch (WebException e)
{
statusCode = e.Message;
}
onCompletion(statusCode);
}
//This method does not help! I just added if it could be any useful
private void onCompletion(string status)
{
if (status == HttpStatusCode.OK.ToString())
MessageBox.Show("file exists");
else
MessageBox.Show("file does not exists");
}
What I need, in detail is...
Download a file from a given URL
Before downloading verify the URL
if (verfied) then
continue downloading and do other tasks
else
fail and stop the process, Don't download! and give a message that URL was broken (couldn't verified)!
I am trying to do the "Verification" part, checking if the URL is correct and waiting for response. I need some kind of STATUS of the verification process, in order to continue.
Should try:
var task = Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
request.EndGetResponse, null);
var response = task.Result;
You can use a ManualResetEventSlim object. Initialize it to true when you instantiate it. At the end of OnComplete method call the Reset method on the ManualResetEventSlim object. In your main application, you just have to invoke the WaitOne method on the ManualResetEventSlim object.
Okay, before I go on, let me state that my background is in web scripting; so applications are very foreign to me. I know very little about .NET and I've been skating by on my limited knowledge.
Anyways, in my application, I have an OAuth httpRequest. The request itself works fine, it gets the data I need from the web API. However, the problem is that whenever I click the button that activates the request, my program freezes for a few seconds until the request is finished. I also have another request which is done automatically every 60 seconds. Which of course means every 60 seconds, my program freezes for a few seconds. How to fix this?
private string twitchCallAPI(string accessKey, string accessSecret, string endpointURI, string httpMethod)
{
OAuthHttpWebRequest httpRequest = new OAuthHttpWebRequest();
httpRequest.ConsumerToken = new OAuthToken { Token = this.twitchConKey, TokenSecret = this.twitchConSecret };
httpRequest.Token = new OAuthToken() { Token = accessKey, TokenSecret = accessSecret };
httpRequest.SetUri(endpointURI);
httpRequest.Method = httpMethod;
try
{
using (var response = httpRequest.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
}
catch (WebException ex)
{
using (var reader = new StreamReader(ex.Response.GetResponseStream()))
{
System.Windows.MessageBox.Show(reader.ReadToEnd());
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.ToString());
}
return string.Empty;
}
You could use a background worker
Shortly said, do request in task and update UI thread with UI synchronization context
TaskFactory.StartNew(()=>
{
//do web request
})
.ContinueWith(() =>
{
this.TextBlock1.Text = "Complete";
}, TaskScheduler.FromCurrentSynchronizationContext());
You can try using Async methods, that is, using a different thread to wait for the response of the request. Its a solution that you can explore.
http://msdn.microsoft.com/en-us/library/86wf6409%28v=vs.100%29.aspx
You can use await keyword:
private async void OnButtonClick()
{
TextBox.Text = await twitchCallAPIAsync(accessKey, accessSecret, endpointURI, httpMethod);
}
The main reason of this is because your application is waiting the methods you launch to finish. You have to take a look at the 'async' concept.
A program executing an 'async' method continue its workflow, and doesn't wait the method to produce a result.