I'm new to Windows Phone 7 development, and am having a bit of trouble finding out how to download some data in the 'background', if you will. I know it is possible, because apps like ESPN, etc, display a "Loading ... .. ." while downloading their data, and the UI is still completely responsive. What I'm trying to do is download some Twitter data.
Here is what I have now, but it is blocking atm:
// Constructor:
// load the twitter data
WebClient twitter = new WebClient();
twitter.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_DownloadStringCompleted);
twitter.DownloadStringAsync(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=badreligion"));
// Callback function:
void twitter_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
XElement xmlTweets = XElement.Parse(e.Result);
TwitterListBox.ItemsSource = from tweet in xmlTweets.Descendants("status")
select new TwitterItem
{
ImageSource = tweet.Element("user").Element("profile_image_url").Value,
Message = tweet.Element("text").Value,
UserName = tweet.Element("user").Element("screen_name").Value
};
}
EDIT: Attempt at multithreading:
// in constructor
Dispatcher.BeginInvoke(new ThreadStart(StartTwitterUpdate));
// other functions
private void StartTwitterUpdate()
{
// load the twitter data
WebClient twitter = new WebClient();
twitter.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_DownloadStringCompleted);
twitter.DownloadStringAsync(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=badreligion"));
}
void twitter_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
XElement xmlTweets = XElement.Parse(e.Result);
TwitterListBox.ItemsSource = from tweet in xmlTweets.Descendants("status")
select new TwitterItem
{
ImageSource = tweet.Element("user").Element("profile_image_url").Value,
Message = tweet.Element("text").Value,
UserName = tweet.Element("user").Element("screen_name").Value
};
}
EDIT 2: Using HttpWebRequest, as suggested by Rico Suter, and with the help of this blog post, I think I've done it:
// constructor
StartTwitterUpdate();
private void StartTwitterUpdate()
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=badreligion"));
request.BeginGetResponse(new AsyncCallback(twitter_DownloadStringCompleted), request);
}
void twitter_DownloadStringCompleted(IAsyncResult asynchronousResult)
{
HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
using (StreamReader streamReader1 =
new StreamReader(response.GetResponseStream()))
{
string resultString = streamReader1.ReadToEnd();
XElement xmlTweets = XElement.Parse(resultString);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
TwitterListBox.ItemsSource = from tweet in xmlTweets.Descendants("status")
select new TwitterItem
{
ImageSource = tweet.Element("user").Element("profile_image_url").Value,
Message = tweet.Element("text").Value,
UserName = "#" + tweet.Element("user").Element("screen_name").Value
};
});
}
}
I think the WebClient methods are partially blocking. The first part including DNS lookup is blocking, but the download itself is not.
See C# async methods still hang UI
Personally I'd call this a bug in the .net API (or even worse: broken by design)
As a workaround you can start the download in a separate thread. I recommend using the tasks API for that.
Task.Factory.StartNew(
()=>
{
twitter.DownloadStringAsync(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=badreligion"));
}
);
Not optimal, since it occupies a thread while performing the DNS lookup, but should be acceptable in practice.
I think another problem with your code is that the callback will not happen on the main thread, but on a threadpool thread. You need to use a SynchronizationContext that posts the event to the main thread.
You have two options: HttpWebRequest and WebClient. Both classes are downloading in the background. Only difference: With WebClient the method twitter_DownloadStringCompleted will be called in UI thread so the parsing of the data will block the UI.
If you use HttpWebRequest the method will be called in another thread but to set the data for data binding or directly to a control you have to use something like this:
void twitter_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
// do your parsing with e.Result...
Deployment.Current.Dispatcher.BeginInvoke(() => {
// set your data (2) to UI
});
}
The code in (2) will be called in UI thread. Set your progressbar to visible in StartTwitterUpdate and set your progress bar to invisible in (2).
Check out this classes to simplify http calls (POST, FILES, GZIP, etc):
http://mytoolkit.codeplex.com/wikipage?title=Http
Related
I try to create a program checking in a lot of PDF. It can be done from a network drive and take few minutes. The application freeze all during the process and I want to avoid that.
I searched in a lot of posts and videos but I failed to implement it in my program. I tried this code to understand how it works but it failed too...
async private void button1_Click(object sender, EventArgs e)
{
rtbx.AppendText($"Processing...\n");
// This webswite takes 1-2s to be loaded
await HeavyWork(#"https://aion.plaync.com/");
rtbx.AppendText($"End.\n");
}
public Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
while (checkBox1.Checked == false)
{
using (WebClient web1 = new WebClient())
{
lesinfos.Add(web1.DownloadString(url));
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
return Task.CompletedTask;
}
When I click the button, I am never able to click in the checkbox, and the UI never respond.
Taking into account that you are forced to use a synchronous API, you can keep the UI responsive by offloading the blocking call to a ThreadPool thread. The tool to use for this purpose is the Task.Run method. This method is specifically designed for offloading work to the ThreadPool. Here is how you can use it:
public async Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
using (WebClient web1 = new WebClient())
{
while (checkBox1.Checked == false)
{
string result = await Task.Run(() => web1.DownloadString(url));
lesinfos.Add(result);
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
}
Notice the async keyword in the signature of the HeavyWork method. Notice the await before the Task.Run call. Notice the absence of the return Task.CompletedTask line at the end.
If you are unfamiliar with the async/await technology, here is a tutorial to get you started: Asynchronous programming with async and await.
Try using DownloadStringTaskAsync(...) with an `await (see example below).
public async Task HeavyWork(string url)
{
List<string> lesinfos = new List<string>();
while (checkBox1.Checked == false)
{
using (WebClient web1 = new WebClient())
{
var content = await web1.DownloadStringTaskAsync(url);
lesinfos.Add(content);
}
rtbx.AppendText($"{lesinfos.Count}\n");
this.Refresh();
}
rtbx.AppendText($"Done !\n");
}
note: WebClient is considered obsolete and it's now recommended to use HttpClient.
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 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 specific problem with WebClient in my Windows Phone app (using MVVM)
private string _lastCurrencyRatesJson;
private bool _lastCurrencyRatesJsonLoaded = false;
private void GetLastCoursesFromApiAsync()
{
var uri = new Uri(string.Format(OperationGetLastCourses, AppSettings.ApiEndpoint, AppSettings.ApiKey));
var client = new WebClient { Encoding = Encoding.UTF8 };
client.DownloadStringCompleted += client_DownloadStringCompleted;
client.DownloadStringAsync(uri);
}
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
_lastCurrencyRatesJson = e.Result;
_lastCurrencyRatesJsonLoaded = true;
}
public List<CurrencyRate> GetLastCourses()
{
var worker = new Thread(GetLastCoursesFromApiAsync);
worker.Start();
while (!_lastCurrencyRatesJsonLoaded)
{
}
.....
The problem is that client_DownloadStringCompleted is never fired BUT when I change GetLastCourses this way:
public List<CurrencyRate> GetLastCourses()
{
var worker = new Thread(GetLastCoursesFromApiAsync);
worker.Start();
// whetever here, but any while...
client_DownloadStringCompleted is fired and data are obtained. It means, connectivity is ok.
I had very similar problems with DownloadStringTaskAsyn. Example:
private async Task<string> GetCoursesForDayFromApiAsJson(DateTime date)
{
var uri = new Uri(string.Format(OperationGetCoursesForDay, AppSettings.ApiEndpoint, AppSettings.ApiKey, date.ToString(DateFormat)));
var client = new WebClient { Encoding = Encoding.UTF8 };
return await client.DownloadStringTaskAsync(uri);
}
Again, at the line with await is application waiting for the data but the DownloadStringTaskAsync is never finished and my UI is still loading.
Any ideas what could be wrong?
SITUATION ONE DAY AGO
So, it looks that WP application is working just with one thread. It means, current thread have to be "finished" and then is DownloadStringTaskAsync finished and the code under the await executed. When I want to work with Task.Result I can not. Never.
When I create another Thread and I am trying to wait for thread completetion (using Join()), created Thread is never finsihed and the code after Join() is never executed.
There is any example on the Internet and I absolutely don't know, why exists some DownloadStringTaskAsync when it is not applicable.
You're blocking the UI thread by your while loop and at the same time, the DownloadStringCompleted event wants to execute on the UI loop. This causes a deadlock, so nothing happens. What you need to do is to let GetLastCourses() return (and whatever method calls that), so that the event handler can execute. This means that the code that handles the results should be in that event handler (not in GetLastCourses()).
With async-await, you didn't provide all of your code, but it's likely that you're encountering pretty much the same issue by calling Wait() or Result on the returned Task. If replace that with await, you code will work. Though that requires you to make all your code from GetCoursesForDayFromApiAsJson() up async.
I'd recommend to use the HttpClient class from Microsoft NuGet package and use the async/await downloading pattern rather than using event-based WebClient class:
Uri uri = new Uri(string.Format(OperationGetLastCourses, AppSettings.ApiEndpoint, AppSettings.ApiKey));
using (HttpClient client = new HttpClient())
{
string result = await client.GetStringAsync(uri);
}
I have a button and a textblock in a Windows 8 "Metro" application. When the button is clicked it calls a webservice via HttpWebRequest.
private void buttonGo_Click(object sender, RoutedEventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost/");
req.BeginGetResponse(ResponseCallback, req);
}
private void ResponseCallback(IAsyncResult asyncResult)
{
HttpWebRequest req = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(asyncResult);
Stream streamResponse = res.GetResponseStream();
StreamReader streamRead = new StreamReader(streamResponse);
string responseString = streamRead.ReadToEnd();
info.Text = responseString; // Can't do this as outside the UI thread
}
I want to update info.Text with the data returned from the WebRequest, however it causes an error: "The application called an interface that was marshalled for a different thread." I understand that this is because it is not being called from the UI thread.
I have found various different solutions involving Dispatcher, SynchronizationContext, the await keyword.
What is the easiest/best way to do this?
Like Damir said we really should use the async/await pattern but sometimes it's simply necessary to update the UI in a worker thread (task). This is done using the current CoreDispatcher that dispatches the invocation to the UI thread:
private void ResponseCallback(IAsyncResult asyncResult)
{
...
this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
info.Text = responseString;
});
}
In a Windows Store app you really should use the async await pattern for all asynchronous calls - it is the simplest and most effective way. You can rewrite your existing code to use this pattern as follows:
private async void buttonGo_Click(object sender, RoutedEventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost/");
HttpWebResponse res = (HttpWebResponse)(await req.GetResponseAsync());
Stream streamResponse = res.GetResponseStream();
StreamReader streamRead = new StreamReader(streamResponse);
// all IO operations should be called asynchronously
string responseString = await streamRead.ReadToEndAsync();
info.Text = responseString; // This way the code runs on UI thread
}
The code after each await keyword is effectively behaving as if it was in a callback but it always executes on the UI thread so that you don't have to worry about it.
All IO bound operations in APIs for Windows Store apps are available in asynchronous flavor. You should prefer them over synchronous ones even when both are available to prevent blocking of UI thread - such as in ReadToEndAsync example above.
Easiest and best:
private async void buttonGo_Click(object sender, RoutedEventArgs e)
{
using (var client = new HttpClient())
{
info.Text = await client.GetStringAsync("http://localhost/");
}
}
Under the covers, await captures the current SynchronizationContext and resumes the method in that context. The Win8/WinRT SynchronizationContext in turn uses the Dispatcher.