How to use WebClient without blocking UI? - c#

Can someone point me to a tutorial or provide some sample code to call the System.Net.WebClient().DownloadString(url) method without freezing the UI while waiting for the result?
I assume this would need to be done with a thread? Is there a simple implementation I can use without too much overhead code?
Thanks!
Implemented DownloadStringAsync, but UI is still freezing. Any ideas?
public void remoteFetch()
{
WebClient client = new WebClient();
// Specify that the DownloadStringCallback2 method gets called
// when the download completes.
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(remoteFetchCallback);
client.DownloadStringAsync(new Uri("http://www.google.com"));
}
public void remoteFetchCallback(Object sender, DownloadStringCompletedEventArgs e)
{
// If the request was not canceled and did not throw
// an exception, display the resource.
if (!e.Cancelled && e.Error == null)
{
string result = (string)e.Result;
MessageBox.Show(result);
}
}

Check out the WebClient.DownloadStringAsync() method, this'll let you make the request asynchronously without blocking the UI thread.
var wc = new WebClient();
wc.DownloadStringCompleted += (s, e) => Console.WriteLine(e.Result);
wc.DownloadStringAsync(new Uri("http://example.com/"));
(Also, don't forget to Dispose() the WebClient object when you're finished)

You could use a BackgroundWorker or as #Fulstow said the DownStringAsynch method.
Here's a tutorial on Backgorund worker: http://www.dotnetperls.com/backgroundworker

Related

Silverlight - Wait for async task to complete

Here is what i have done so far:
All of the below code will get looped through and different URL's will be sent each time. I want to be able to call the address in the loop and then wait for it to complete and then call the completed method.
I have created my URI
Uri address = new Uri("http://dev.virtualearth.net/REST/V1/Imagery/Metadata/OrdnanceSurvey/" + latitude + "," + longitude + "?+zl=" + zoomLevel + "&key=""");
I have then told it where to call when the operation has complete
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += WebClientDownloadString_Complete;
webClient.DownloadStringAsync(address);
Then i set up what to happen when the operation has completed
private void WebClientDownloadString_Complete(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
string html = e.Result;
string[] parts = html.Split(',');
string[] URLs = parts[7].Split('"');
URL = URLs[3].Replace("{subdomain}", "t0").Replace("{quadkey}", qk.Key).Replace(#"\", string.Empty);
}
}
Is there a way so that when the webclient calls the URL I wait till the operation has completed and it calls the completed method?
The easy way to do this is to just use DownloadString instead of DownloadStringAsync. The DownloadString call will block (make sure to run it on a background thread!) and you can loop as you expect.
Your other option is to create a TaskCompletionSource and set it in your event handler.
private TaskCompletionSource<bool> currentTask;
private async Task GetURLs()
{
while (someCondition)
{
currentTask = new TaskCompletionSource<bool>();
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += WebClientDownloadString_Complete;
webClient.DownloadStringAsync(address);
await currentTask;
}
}
private void WebClientDownloadString_Complete(...)
{
//Process completion
//C# 6 null check
curentTask?.TrySetValue(true);
}
"GetURLS" will block asynchronously until the tasks value is set, and then allow the while loop to continue. This has the advantage of not needing an explicit background thread.
The code that you put up already does just that - you created your WebClient, you set an event handler to its completed method, and you start the download. If you want to wait for it to finish, all you need to do is let your method end, and control will pass back to the Silverlight framework which will do your waiting for you.
The other way to do this would be to use the async and await keywords, which your framework appears to support. Simply put, you might be able to put an await keyword before your method call to DownloadStringAsync, which will cause the system to wait for the download (and go on to other tasks), pull out the string once the download completes, and continue as usual:
string html = await webClient.DownloadStringAsync(address);
Wanted to do this myself some time ago.
Here is how I did it:
// create a notifier which tells us when the download is finished
// in .net 4.5 DownloadFileTaskAsync can be used instead which simplyfies this task
AutoResetEvent notifier = new AutoResetEvent(false);
client.DownloadFileCompleted += (object sender, AsyncCompletedEventArgs e) =>
{
notifier.Set();
};
try
{
client.DownloadFileAsync(downloadUrl, destination);
// wait for download to finish
notifier.WaitOne();
...

WebClient.DownloadFileAsync in ApiController does not trigger events

I have a class that downloads files that works in a console application and now I want to use it in a webapi project.
To download a file I use
WebClient wc = new WebClient();
bool downloadFinished = false;
wc.DownloadProgressChanged += (s, e) =>
{
ProgressChanged(e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
};
wc.DownloadFileCompleted += (s, e) =>
{
downloadFinished = true;
};
wc.DownloadFileAsync(new Uri(fileUrl), Path.Combine(DOWNLOADFOLDERTEMP, FileName));
while (!downloadFinished)
{
System.Threading.Thread.Sleep(250);
}
Which works in a console program, but not inside an ApiController.
It downloads the file, but the progress and completed events never trigger, resulting in an endless loop.
How do I make WebClient trigger events when called in an ApiController?
WebClient uses the current SynchronizationContext to posts its events into. You are not releasing the current thread by busy-waiting. I don't know why you are performing the download asynchronously and then waiting for the result. What good does this do? Just use the synchronous API.
At least, do not spin-wait. Use WebAPI's support for async to IO to get rid of the waiting loop. Use async and await. You'll find on the web how to do that with WebClient.
Your use of downloadFinished is not thread-safe. But you shouldn't need such a variable anyway.

Windows Phone BackgroundWorker for WebClient?

Now the WebClient issue is fixed and can return on a background thread i'd like to start using it in this way.
After much searching I've come up with this code that appears to work fine, is this all there is to it?
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s,e) =>
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += DownloadStringCompleted;
wc.DownloadStringAsync(url);
};
bw.RunWorkerAsync();
In DownloadStringCompleted I send the result back to the UI thread.
Have I missed anything important or is it really this simple?
I don't get why you would want to run the WebClient on a background thread in the first place, since the WebClient is already creating a thread for the downloading part.
The difference is that the WebClient is running it's DownloadStringCompleted event on the UI thread. Which it still would do in your code.
I would suggest you use WebRequest class instead. The use of the WebRequest class can be greatly simplified with a simple extension method, that makes it behave like the WebClient.
public static class WebRequestEx
{
public static void DownloadStringAsync(this WebRequest request, Action<string> callback)
{
if (request == null)
throw new ArgumentNullException("request");
if (callback == null)
throw new ArgumentNullException("callback");
request.BeginGetResponse((IAsyncResult result) =>
{
try
{
var response = request.EndGetResponse(result);
using (var reader = new StreamReader(response.GetResponseStream()))
{
callback(reader.ReadToEnd());
}
}
catch (WebException e)
{
// Don't perform a callback, as this error is mostly due to
// there being no internet connection available.
System.Diagnostics.Debug.WriteLine(e.Message);
}
}, request);
}
}
The issue I referred to was that in 7.0 WebClient always returned on the UI thread regardless of where it was created, potentially making the UI unresponsive.
In the WP SDK 7.1 WebClient will return on the thread it was created from, so if it is created from a background thread DownloadStringCompleted will now return on a background thread.
If you test my example without marshalling the response you will see an Invalid Cross Thread Exception.
It seems to me unless you have a reason not to, why not use WebClient now?
Seems that easy.
Check only if everything which can be disposed get's disposed.

Accessing controls through threads

I use WebBrowser Control in c# to check a couple search engine results but sometime for no reason it gets stuck. My first thought was to make each searching function as a thread and use thread.abort() from a timer but I just couldn't handle with the UI Controls (including the WebBrowser) no matter what I've tried.
Anyone has a solution for me? an example would be great cause I already tried so many things and I keep getting all these exceptions.
I believe you can use WebRequest in a Background worker & avoid the difficulties of dealing with the COM threading model. You can use WebRequest.Timeout to handle anything you think is taking too long.
Try something like this:
static void Main(string[] args)
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += backgroundWorker_DoWork;
bg.RunWorkerAsync(new List<object> { "http://download.thinkbroadband.com/512MB.zip" });
while (true) {}
}
private static void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Starting operation.");
BackgroundWorker bw = sender as BackgroundWorker;
List<object> args = (List<object>)e.Argument;
var url = (string)args[0];
WebRequest request = WebRequest.Create(url);
request.Timeout = 300;
try
{
WebResponse response = request.GetResponse();
Console.WriteLine("Request successful.");
}
catch (Exception ex)
{
Console.WriteLine("Request timed out.");
}
}
WebBrowser is a component you should use when you want to embed an instance of IE into your presentation layer. Unless you need this, you can use something more lightwweight.
Use the control's .Invoke() method: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
There are other methods you can use too: .BeginInvoke and .EndInvoke for asynchronous invokes.
if(control.InvokeRequired)
{
control.Invoke(control.delegate);
}

Is it possible to "join" a DownloadStringAsync operation?

I have this code:
public static String Download(string address) {
WebClient client = new WebClient();
Uri uri = new Uri(address);
// Specify a progress notification handler.
client.DownloadProgressChanged += (_sender, _e) => {
//
};
// ToDo: DownloadStringCompleted event
client.DownloadStringAsync(uri);
}
Instead of having the rest of my code execute in the DownloadStringCompleted event handler when the download is complete, can I somehow Join this Async request? It will be housed in another thread (doing it this way so I have access to the download progress). I know that DownloadStringAsync can take a second parameter; an object called userToken in the manual. Could this be of use? Thank you,
You could use a manual reset event:
class Program
{
static ManualResetEvent _manualReset = new ManualResetEvent(false);
static void Main()
{
WebClient client = new WebClient();
Uri uri = new Uri("http://www.google.com");
client.DownloadProgressChanged += (_sender, _e) =>
{
//
};
client.DownloadStringCompleted += (_sender, _e) =>
{
if (_e.Error == null)
{
// do something with the results
Console.WriteLine(_e.Result);
}
// signal the event
_manualReset.Set();
};
// start the asynchronous operation
client.DownloadStringAsync(uri);
// block the main thread until the event is signaled
// or until 30 seconds have passed and then unblock
if (!_manualReset.WaitOne(TimeSpan.FromSeconds(30)))
{
// timed out ...
}
}
}
My first thought would be to use DownloadString, the synchronous version of DownloadStringAsync. However, it seems that you must use an async method to get the progress notification. Okay, that's no big deal. Just subscribe to DownloadStringCompleted and use a simple wait handle like ManualResetEventSlim to block until it completes.
One note, I am not sure if the progress notification is even raised for DownloadStringAsync. According to MSDN, DownloadProgressChanged is associated with some of the async methods, but not DownloadStringAsync.

Categories

Resources