Is it possible to "join" a DownloadStringAsync operation? - c#

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.

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();
...

AutoResetEvent use issue

I'm trying to use an AutoResetEvent object to block the thread until the async. download of a WebClient is done.
My problem is that once I call WaitOne(), the thread just locks there and VS never reaches the breakpoint in the DownloadComplete event handler method.
Here's my code
//Class used to pass arguments to WebClient's events...
public class RunArgs
{
public JobInfo jobInfo;
public int jobTotal;
public int jobIndex;
public AutoResetEvent AutoResetEventObject;
}
List<JobInfo> jl = ConfigSectionWrapper.GetAllJobs();
int jobAmount = jl.Count;
int jobIndex = 0;
RunArgs args = new RunArgs();
args.jobTotal = jl.Count;
foreach (JobInfo ji in jl)
{
if (ji.enabled == "0")
{
args.jobIndex++;
continue;
}
try
{
args.jobIndex++;
args.jobInfo = ji;
appLog.Source = ji.eventSource;
appLog.WriteEntry(string.Format("Started job {0}...", ji.jobName), EventLogEntryType.Information);
ji.fullFileName = string.Format(ji.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
ji.fullFileName = string.Format("{0}{1}", ji.downloadDirectory, ji.fullFileName);
using (WebClient wc = new WebClient())
{
AutoResetEvent notifier = new AutoResetEvent(false);
args.AutoResetEventObject = notifier;
wc.Credentials = CredentialCache.DefaultNetworkCredentials;
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...
notifier.WaitOne();
}
}
catch (Exception ex)
{
appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
DeleteFile(ji.fullFileName);
}
}
private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
RunArgs args = (RunArgs)e.UserState;
//Do things....
args.AutoResetEventObject.Set();
}
So I instantiate notifier with false in the constructor, because I don't want its status to be signaled already. Unless I'm reading MSDN wrong ?
Anything obviously wrong ?
WebClient uses the AsyncOperationManager to manage async operations. So, make sure you are aware of how these async operations get called under different scenarios.
Under WinForms
Under a WinForm application AsyncOperationManager uses the WindowsFormsSynchronizationContext. As #Michael says, the WaitOne() call blocks the main form thread from firing the DownloadCompleted event. In WinForms the DownloadCompleted is executed on the main WinForm thread.
So, remove the notifier.WaitOne() and it should work. DownloadCompleted needs to be called by the main window thread (presumably, the one which you're blocking with WaitOne()).
Under Console applications
Under a Console type application the AsyncOperationManager uses System.Threading.SynchronizationContext and the DownloadCompleted is executed asynchronously by a thread form the threadpool.
There is no issue with calling notifier.WaitOne() under a Console app; and the code above works as expected.
I haven't found any documentation to support this, but looking at the WebClient code in Reflector, it appears that the events are raised on the main thread, not the background thread. Since your main thread is blocking when you call notifier.WaitOne(), the event handler never gets called.
If the example you provided accurately represents your code, there's absolutely no need to use wc.DownloadFileAsync() instead of wc.DownloadFile(). That notifier.WaitOne() call ultimately makes this into a synchronous operation. If you're looking for a true asynchronous operation, you'll have to do this differently.
Here's what I ended up doing:
private AutoResetEvent notifier = new AutoResetEvent(false);
Now the main loop looks like:
foreach (JobInfo ji in jl)
{
if (ji.enabled == "0")
{
args.jobIndex++;
continue;
}
args.jobInfo = ji;
Thread t = new Thread(new ParameterizedThreadStart(startDownload));
t.Start(args);
notifier.WaitOne();
}
private void startDownload(object startArgs)
{
RunArgs args = (RunArgs)startArgs;
try
{
args.jobIndex++;
appLog.Source = args.jobInfo.eventSource;
appLog.WriteEntry(string.Format("Started job {0}...", args.jobInfo.jobName), EventLogEntryType.Information);
args.jobInfo.fullFileName = string.Format(args.jobInfo.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
args.jobInfo.fullFileName = string.Format("{0}{1}", args.jobInfo.downloadDirectory, args.jobInfo.fullFileName);
WebClient wc = new WebClient();
wc.Credentials = CredentialCache.DefaultNetworkCredentials;
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...
}
catch (Exception ex)
{
appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
DeleteFile(args.jobInfo.fullFileName);
notifier.Set();
}
}
So now the AutoResetEvent is blocking the main thread but t can successfully trigger the DownloadFileCompleteEvent. And in the DownloadFileCompleted event, I'm obviously doing a notifier.Set() too.
Thanks all!

C# async method that also has anonymous callback handlers does not flow correctly

Below is the code for a C# async method that also has callback handlers for two of the WebClient control's events: DownloadProgressChanged and OpenReadCompleted. When I run the code, initially it flows to the "await DownloadStringTaskAsync()" call and exits. Then I see the anonymous event handler code fire for DownloadProgressChanged, and this is where I have a problem. The code then flows to the "return strRet" statement so the return for the method is the initialization value "(none)" assigned to strRet at the top of the method, instead of the contents of the web page that is assigned to strRet in the OpenReadCompleted anonymous callback.
So I need to wait for the OpenReadCompleted callback to execute before control flows to the return statement, but I'm not sure how to do this properly. How do I correct the code so that it does not reach the "return strRet" statement until the OpenReadCompleted callback has executed?
/// <summary>
/// This method downloads the contents of a URL to a string. Returns the URL contents
/// as a string if it succeeds, throws an Exception if not.
/// <param name="strUrl">The URL to download.</param>
/// <param name="progress">An IProgress object to report download progress to. May be NULL.</param>
/// <param name="cancelToken">A cancellation token. May be NULL.</param>
/// <param name="iNumSecondsToWait">The number of seconds to wait before cancelling the download. Default is 30 seconds</param>
/// </summary>
/// <remarks>
/// Use "await" with this method wrapped in Task.run() to manage the process asynchronously.
///
/// NOTE: The DownloadProgressChanged() event is raised on the UI
/// thread so it is safe to do UI updates from the IProgress.Report()
/// method.
/// </remarks>
async public static Task<string> URLToString(string strUrl, IProgress<int> progress, CancellationToken cancelToken, int iNumSecondsToWait = 30)
{
// The string to be returned.
string strRet = "(none)";
strUrl = strUrl.Trim();
if (String.IsNullOrWhiteSpace(strUrl))
throw new ArgumentException("(Misc::URLToString) The URL is empty.");
if (iNumSecondsToWait < 1)
throw new ArgumentException("(Misc::URLToString) The number of seconds to wait is less than 1.");
// Asynchronous download. Note, the Silverlight version of WebClient does *not* implement
// IDisposable.
WebClient wc = new WebClient();
// Create a download progress changed handler so we can pass on progress
// reports to the caller if they provided a progress report object.
// This event is raised on the UI thread.
wc.DownloadProgressChanged += (s, e) =>
{
// Do we have a progress report handler?
if (progress != null)
// Yes, call it.
progress.Report(e.ProgressPercentage);
// If we have a cancellation token and the operation was cancelled, then abort the download.
if (cancelToken != null)
cancelToken.ThrowIfCancellationRequested();
}; // wc.DownloadProgressChanged += (s, e) =>
// Use a Lambda expression for the "completed" handler
// that writes the downloaded contents as a string to a file.
wc.OpenReadCompleted += (s, e) =>
{
// If we have a cancellation token and the operation was cancelled, then abort the download.
if (cancelToken != null)
cancelToken.ThrowIfCancellationRequested();
// Return the downloaded file as a string.
strRet = e.Result.ToString();
}; // wc.OpenReadCompleted += (s, e) =>
// Now make the call to download the file and do an asynchronous wait for the result.
await wc.DownloadStringTaskAsync(new Uri(strUrl));
// wc.DownloadStringAsync(new Uri(strUrl));
return strRet;
} // async public static void URLToStr
================================
UPDATE: Based on the answers I received I have modified the code to the following:
async public static Task<string> URLToStringAsync(string strUrl, IProgress<int> progress, CancellationToken cancelToken, int iNumSecondsToWait = 30)
{
strUrl = strUrl.Trim();
if (String.IsNullOrWhiteSpace(strUrl))
throw new ArgumentException("(Misc::URLToStringAsync) The URL is empty.");
if (iNumSecondsToWait < 1)
throw new ArgumentException("(Misc::URLToStringAsync) The number of seconds to wait is less than 1.");
// Asynchronous download. Note, the Silverlight version of WebClient does *not* implement
// IDisposable.
WebClient wc = new WebClient();
// Create a download progress changed handler so we can pass on progress
// reports to the caller if they provided a progress report object.
// This event is raised on the UI thread.
wc.DownloadProgressChanged += (s, e) =>
{
// Do we have a progress report handler?
if (progress != null)
// Yes, call it.
progress.Report(e.ProgressPercentage);
// If we have a cancellation token and the operation was cancelled, then abort the download.
if (safeCancellationCheck(cancelToken))
wc.CancelAsync();
}; // wc.DownloadProgressChanged += (s, e) =>
// Now make the call to download the file and do an asynchronous wait for the result.
return await wc.DownloadStringTaskAsync(new Uri(strUrl));
} // async public static void URLToStringAsync
I found several issues:
a) From MSDN documentation it looks like that DownloadStringTaskAsync does not fire DownloadProgressChanged
b) OpenReadCompleted event will be fired only if you will create request with OpenReadAsync. It will not be fired for DownloadStringTaskAsync.
c) You can use DownloadStringCompleted event to get the result of DownloadStringTaskAsync, but why if you are using async/await you can do just:
strRet = await wc.DownloadStringTaskAsync(new Uri(strUrl));
You're mixing several different asynchronous APIs. DownloadProgressChanged and OpenReadCompleted are both EAP events, while DownloadStringTaskAsync is a TAP method.
I recommend that you either consistently use the EAP API or the TAP API. Better yet, convert from WebClient to HttpClient.
BTW, you probably don't want to call ThrowIfCancellationRequested from an event handler. Instead, wire your CancellationToken to WebClient.CancelAsync.

How to avoid having a Thread.Sleep(Int32.MaxValue) when waiting for an asynchronous operation to end in a Console app?

I have the following code that will download a file asynchronously to my hard-drive, shouting to the console the current progress and quitting with a goodbye message in the end:
webClient.DownloadProgressChanged.Add(fun args ->
if (currentPercentage < args.ProgressPercentage) then
Console.WriteLine(args.ProgressPercentage.ToString() + "%")
currentPercentage <- args.ProgressPercentage
)
webClient.DownloadFileCompleted.Add(fun args ->
Console.WriteLine("Download finished!")
Environment.Exit 0
)
webClient.DownloadFileAsync(new Uri(url_to_download), file_name)
Thread.Sleep Int32.MaxValue
I was wondering, though, whether there could be any more elegant way of achieving this without having to resort to "sleeping forever" in the main thread, having the program end though an Environment.Exit(). I have no prejudice towards using Environment.Exit() but I'd like to avoid it, if possible! The only way I can think of avoiding this would be to spawn a new thread and then waiting for it to die, but that does seem cumbersome. Any simpler way to accomplish this?
You can use a ResetEvent like this:
webClient.DownloadProgressChanged += (f,a) => ...
AutoResetEvent resetEvent = new AutoResetEvent(false);
webClient.DownloadFileCompleted += (f, a) => resetEvent.Set();
webClient.DownloadDataAsync(new Uri(url_to_download), file_name);
resetEvent.WaitOne();
Console.WriteLine("Finished");
simply use a waithandle derived class like the mutex to signal you're ready to close down. signal it in your download completed method and wait for it at the end of your app. as it becomes signalled your app will exit naturally.
In case you are a fan of the Reactive extensions library (Rx), then this process can be modeled in terms of observable like:
public static IObservable<int> DownloadURL(string url,string fname)
{
return Observable.Defer(() =>
{
var sub = new Subject<int>();
var wc = new WebClient();
wc.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
sub.OnNext(e.ProgressPercentage);
if (e.ProgressPercentage == 100)
sub.OnCompleted();
};
wc.DownloadFileAsync(new Uri(url), fname);
return sub;
});
}
public static void Main(string[] str)
{
foreach (var i in DownloadURL("http://www.google.com", "g:\\google.html").DistinctUntilChanged().ToEnumerable())
Console.WriteLine(i);
}
In C# you could write an extension method for WebClient that waits for the download to complete, while still pitching update events:
static class WebClientEx {
public static void DownloadSemiSync(this WebClient webClient, Uri address, string filename) {
var evt = new AutoResetEvent(false);
webClient.DownloadFileCompleted += (s, e) => evt.Set();
webClient.DownloadFileAsync(address, filename);
evt.WaitOne();
}
}
This'd allow you to define whatever progress event you want on it and then use it as a synchronous function, reducing your main code to this:
static void Main() {
var webClient = new WebClient();
webClient.DownloadProgressChanged += (s, args) => {..};
webClient.DownloadSemiSync(new Uri("http://.."), "test.bin");
Console.WriteLine("DownloadFinished");
}
Throws all the events, but then waits to exit.

How to use WebClient without blocking UI?

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

Categories

Resources