I want to download data non asynchronously in a Windows Phone application. I would make a downloader class, and have a simple method in it to download a string from a URL. On other platforms, I would use:
public class TextDownloader
{
public string GetString(string url)
{
WebClient web = new WebClient();
string s = web.DownloadString("http://www.google.com");
return s;
}
}
It would work well: simple, minimal code. However, the WebClient.DownloadString method is not available on Windows Phone 7, nor many WebRequest options. Are there any alternative ways to download data non asynchronously in Windows Phone? I would rather not have to create multiple events for download and error, just have a simple method return a value or throw an exception.
Indeed, you cannot use the synchronous model for downloads with WebClient out-of-the-box. This is by design, and given the nature of Windows Phone applications, you should follow this methodology.
The solution to your problem - callbacks. You can easily refactor your function to something like this:
public void GetString(string url, Action<string> onCompletion = null)
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (onCompletion != null)
onCompletion(e.Result);
};
client.DownloadStringAsync(new Uri(url));
}
This makes it relatively easy to use and trigger an action when it is completed. There is another way to do this as well, and that is - async/await. You will need to install the Bcl.Async package through NuGet:
Install-Package Microsoft.Bcl.Async -Pre
It would let you do this:
public async Task<string> DownloadString(string url)
{
WebClient client = new WebClient();
return await client.DownloadStringTaskAsync(url);
}
Still, though, it will be bound to the asynchronous model, just wrapped in a different manner and the thread will be waiting to get the string before returning it back to the caller.
Related
I am working on an asp.net mvc5 web application, and i am not sure what are the differences between using DownloadStringTaskAsync() & usingDownloadStringAsync() . for example if i have the following webclient :-
using (WebClient wc = new WebClient())
{
string url = currentURL + "home/scanserver?tokenfromtms=" + "12345" + "&FQDN=allscan" ;
var json = await wc.DownloadStringTaskAsync(url);
TempData["messagePartial"] = string.Format("Scan has been completed. Scan reported generated");
}
will there be any differences if i chnage DownloadStringTaskAsync(url); to DownloadStringAsync(url); ??
WebClient.DownloadStringAsync is using the older event-based asynchronous pattern (EAP).
WebClient.DownloadStringTaskAsync is using the newer task-based asynchronous pattern (TAP).
Since your code is already using async/await, I recommend you stick with the TAP method. TAP code is more maintainable than EAP code.
You could also go another step further and consider using HttpClient instead of WebClient. The odd naming in WebClient is due to it supporting both synchronous and EAP, and then later updated to include TAP. In contrast, HttpClient is a newer type that was based on TAP right from the beginning, so its API is cleaner.
The DownloadStringTaskAsync and DownloadStringAsync documentation do a pretty good job of highlighting both similarities and differences.
They both are non-blocking, asynchronous methods. However, DownloadStringAsync has a return signature of void and requires you to listen to the DownloadStringCompleted event to obtain your results from Result, whereas the DownloadStringTaskAsync method returns a Task<string>.
The latter is useful if you have parallel asynchronous operations that you need to await before continuing or if you want to call ContinueWith on the operation once it's completed. In addition, with the latter you'll also need to retrieve the result from the task once the task is in a completed state which can be unwrapped with await.
Finally, DownloadStringAsync requires a URI whereas DownloadStringTaskAsync will accept a string.
For ease of use, DownloadStringTaskAsync will probably work just fine, provided that you've placed it in an async method as follows:
void Main()
{
using (WebClient wc = new WebClient())
{
var json = GetGoogleFromTask(wc);
json.Dump();
}
}
public async Task<string> GetGoogleFromTask(WebClient wc)
{
string url = "http://www.google.com" ;
var json = await wc.DownloadStringTaskAsync(url);
return json;
}
Alternatively, you can also return just the Task so that you can continue other operations without needing an async method that awaits the return:
void Main()
{
using (WebClient wc = new WebClient())
{
var json = GetGoogleFromTask(wc);
json.Dump();
}
}
public Task<string> GetGoogleFromTask(WebClient wc)
{
string url = "http://www.google.com" ;
var json = wc.DownloadStringTaskAsync(url);
return json;
}
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 an identical method in two of my WP8 apps. Given the same url and same device, the method works on one app, but not the other. In the failing app, GetAsync is hanging after it is called. No time out, no exception.
Here is the method in question.
private async Task<Byte[]> DownloadData(string uri)
{
byte[] myDataBuffer = null;
var newUri = new Uri(uri, UriKind.Absolute);
var myWebClient = new HttpClient();
var response = await myWebClient.GetAsync(newUri);
if (response.Content.Headers.ContentType.MediaType == "text/plain"
|| response.Content.Headers.ContentLength < 200)
{
throw new Exception(await response.Content.ReadAsStringAsync());
}
myDataBuffer = await response.Content.ReadAsByteArrayAsync();
return myDataBuffer;
}
This happens every time on one particular app, but not the other. Same device. Has anybody ever experienced this behavior? The url is valid, the code is identical. Is there a project setting somewhere that might affect this? I'm using the HttpClient in another portion of the failing app and it works there.
I can change the code to use a HttpWebRequest and that works fine. Just not the HttpClient.
I just now discovered that if I copy the method into my button_click handler, it works there too. Is there a problem having this method inside a separate class? That seems odd to me.
update
What seems to be breaking it is multiple layers of async methods calling it. Within the class I have
public override byte[] GetImageData(string imageUri)
{
return GetImageDataAsync(imageUri).Result;
}
public async Task<byte[]> GetImageDataAsync(string imageUri)
{
return await DownloadData(imageUri);
}
from my button_click handler, I'm calling GetImageData(uri). If I change that to await GetImageDataAsync(uri) it works.
Is Result not the correct property to reference in GetImageData?
Here's a test url "http://www.rei.com/pix/common/REI_logo.gif"
Calling Result or Wait can cause deadlocks, as I explain on my blog.
The proper way to solve this is to use await. I assume that there's some reason you want to synchronously block, but it's better to use await and find a way to make it work in your code.
Even though I am creating new instances of WebClient, and following standard procedures to ensure that the WebClient is removed by GC, which shouldn't even matter: the webclient retrieves content from my server that it previously retrieved, and only restarting the app will it allow new content from my server to be retrieved. The content is a simple text file string, no fancy caching since it works on WinRT just fine.
This is a mystery, as I am trying to make a ChatBox; I need to refresh to gain new content yet the WebClient returns content it retrieved the first time.
My Code:
public async Task<string> RetrieveDocURL(Uri uri)
{
return await DownloadStringTask(new WebClient(), uri);
}
/*
public async Task<byte[]> RetrieveImageURL(string url)
{
return await _webClient.GetByteArrayAsync(url);
}
* */
private Task<string> DownloadStringTask(WebClient client, Uri uri)
{
var tcs = new TaskCompletionSource<string>();
client.DownloadStringCompleted += (o, e) =>
{
if (e.Error != null)
tcs.SetException(e.Error);
else
tcs.SetResult(e.Result);
};
client.DownloadStringAsync(uri);
return tcs.Task;
}
The WebClient's caching strategy is really aggressive. If you're querying the same URL each time, you should consider adding a random parameter at the end. Something like:
"http://www.yourserver.com/yourService/?nocache=" + DateTime.UtcNow.Ticks
I’m developing a Windows Phone 7.1 application, and trying to implement tombstoning.
Due to the legal reasons I can’t save my view model. I’m only saving encrypted session ID, which can be used to load a view model data from the remote server.
On resume, I need to verify the session ID, if it’s expired – I take user to the login page of my app, if it’s still OK, I reload view model data from the server.
The problem is the HttpWebRequest lacks blocking API. Moreover, while inside page.OnNavigatedTo method after de-tombstoning, the method described here blocks forever.
I’ve worked around the problem by presenting my own splash screen.
However, I’d rather like to complete those RPC calls while the system-provided “Resuming…” splash screen is visible, i.e. before I return from page.OnNavigatedTo method.
Any ideas how can I complete HTTP requests synchronously while inside page.OnNavigatedTo after de-tombstoning?
Let me start out by saying that Microsoft really tries to push you to do async calls for good reasons, which is why I wanted to emphasize it.
Now if you really want to do it synchronous, I have an idea which I haven't been able to test myself. When using the HttpWebRequest class, there are two important functions, which you've probably used as well: BeginGetResponse and EndGetResponse.
These two functions work closely together. BeginGetResponse starts a asynchronous webrequest, where when the request is finished the EndGetResponse gives you to ouput when it's done. This is the way MS tries to let you do it. The trick to doing this stuff synchronously is that the beginGetResponse returns a IAsyncResult. This IAsyncResult interface contains a WaitHandler, which can be used to synchronously wait until the request is done. After which you can just continue with the endGetRequest and go on with your bussiness. The same thing goes for the BeginGetRequestStream and EndGetRequestStream.
But as I said before, I haven't tested this solution and it's purely theoretical. Let me know if it worked or not.
Good luck!
Update: another option is to use Reactive Extensions.
If you're on VS2010 you can install the AsyncCTP and when you do an extension method gets added that allows you to await the response.
static async Task<Stream> AsynchronousDownload(string url)
{
WebRequest request = WebRequest.Create(url);
WebResponse response = await request.GetResponseAsync();
return (response.GetResponseStream());
}
then:
UPDATED:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var myResponse = await AsynchronousDownload("http://stackoverflow.com");
}
or
If you're using VS2012 you can install the Microsoft.Bcl.Async lib and do the same thing as if you were using the AsyncCTP, await the response.
or
You could implement something similar to Coroutines in Caliburn Micro. For this you implement the IResult interface.
public interface IResult
{
void Execute(ActionExecutionContext context);
event EventHandler<ResultCompletionEventArgs> Completed;
}
A possible implementation:
public class HttpWebRequestResult : IResult
{
public HttpWebRequest HttpWebRequest { get; set; }
public string Result { get; set; }
public HttpWebRequestResult(string url)
{
HttpWebRequest = (HttpWebRequest) HttpWebRequest.Create(url);
}
public void Execute (ActionExecutionContext context)
{
HttpWebRequest.BeginGetResponse (Callback, HttpWebRequest);
}
public void Callback (IAsyncResult asyncResult)
{
var httpWebRequest = (HttpWebRequest)asyncResult.AsyncState;
var httpWebResponse = (HttpWebResponse) httpWebRequest.EndGetResponse(asyncResult);
using (var reader = new StreamReader(httpWebResponse.GetResponseStream()))
Result = reader.ReadToEnd();
Completed (this, new ResultCompletionEventArgs ());
}
public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
}
Then to call it:
var httpWebRequestResult = new HttpWebRequestResult("http://www.google.com");
yield return httpWebRequestResult;
var result = httpWebRequestResult.Result;
This might be an example of grabbing the Coroutines implementation from CM and using it separately.