Silverlight - Wait for async task to complete - c#

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

Related

Wait for BackgroundTask to complete

I have a BackgroundTask which generates some text and then saves it as a file to a LocalFolder. I need to get this file with my main project (same VS solution) after it's been generated by the BackgroundTask and do some next work with it. The background task is triggered both manually by the user (via a "Reload" button) and every 15 mins by a TimeTrigger.
This is the relevant code snippet:
syncTrigger.RequestAsync();
articles = await getCachedArticles("");
How can I tell the getCachedArticles method to wait until after previous request finishes before running? Thank you very much!
If I understand it correctly, what you need to do is to wait for the BackgroundTaskRegistration.Completed event to fire.
One approach would be to create an extension method that returns a Task that completes when the event fires:
public static Task<BackgroundTaskCompletedEventArgs> CompletedAsync(
this BackgroundTaskRegistration registration)
{
var tcs = new TaskCompletionSource<BackgroundTaskCompletedEventArgs>();
BackgroundTaskCompletedEventHandler handler = (s, e) =>
{
tcs.SetResult(e);
registration.Completed -= handler;
};
registration.Completed += handler;
return tcs.Task;
}
You would then use it like this:
var taskCompleted = registration.CompletedAsync();
await syncTrigger.RequestAsync();
await taskCompleted;
articles = await getCachedArticles("");
Note that the code calls CompletedAsync() before calling RequestAsync() to make sure the even handler is registered before the task is triggered, to avoid the race condition where the task completes before the handler is registered.
Whenever you want your current context to wait for an asynchronous method to complete before continuing, you want to use the await keyword:
await syncTrigger.RequestAsync(); //the line below will not be executed until syncTrigger.RequestAsync() completes its task
articles = await getCachedArticles("");
I would recommend reading through the await C# Reference Article in order to get the full picture of how it works.
EDIT: svick's answer shows the best approach, I even wrote a blog post about it. Below is my original answer with a couple of indirect alternatives that might work for some cases.
As others have noted, awaiting syncTrigger.RequestAsync() won't help, although it is a good idea nevertheless. It will resume execution when the background task was succesfully triggered, and as such allow you to check if it failed for any reason.
You could create an app service to make it work when you are triggering the background task from the application. App services behave similar to web services. They are running in a background task, but have request-response semantics.
In the background service you would need to handle the RequestReceived event:
public void Run(IBackgroundTaskInstance taskInstance)
{
var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceconnection = details.AppServiceConnection;
appServiceconnection.RequestReceived += OnRequestReceived;
}
private async void OnRequestReceived(AppServiceConnection sender,
AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
ValueSet arguments = args.Request.Message;
ValueSet result = new ValueSet();
// read values from arguments
// do the processing
// put data for the caller in result
await args.Request.SendResponseAsync(result);
messageDeferral.Complete();
}
In the client you can then call the app service:
var inventoryService = new AppServiceConnection();
inventoryService.AppServiceName = "from manifest";
inventoryService.PackageFamilyName = "from manifest";
var status = await inventoryService.OpenAsync();
var arguments = new ValueSet();
// set the arguments
var response = await inventoryService.SendMessageAsync(arguments);
if (response.Status == AppServiceResponseStatus.Success)
{
var result = response.Message;
// read data from the result
}
Check the linked page for more information about app services.
You could call the same app service from the scheduled background task as well, but there would be no way to get notified when the processing was completed in this case.
Since you've mentioned that you're exchanging data via a file in LocalFolder your application could try monitoring the changes to that file instead:
private async Task Init()
{
var storageFolder = ApplicationData.Current.LocalFolder;
var monitor = storageFolder.CreateFileQuery();
monitor.ContentsChanged += MonitorContentsChanged;
var files = await monitor.GetFilesAsync();
}
private void MonitorContentsChanged(IStorageQueryResultBase sender, object args)
{
// react to the file change - should mean the background task completed
}
As far as I know you can only monitor for all the changes in a folder and can't really determine what changed inside the event handler, so for your case it would be best to have a separate sub folder containing only the file saved by the background task once it completes. This way the event would only get raised when you need it to.
You'll have to check for yourself whether this approach works reliably enough for you, though.
You need to wait the RequestAsync method completed with the result
https://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.background.devicetriggerresult
after that I suggest to wait a few seconds with task.delay and try to get the data.
Update:
When you have the device trigger result you need to check this result before to try to get the data after that you can suppose that you have the data saved. I suggested before to use Task.Delay just to wait a few seconds to be sure all data is saved because sometimes the process take some milliseconds more than is expected. I did this because we don't have an event like TriggerCompleted I needed to create my own approach.
I did this before in my app and it works very well.

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.

C# Method returning before event handler is called

My uriRead method appears to return before the Asynchronous download is completed, resulting in "" being returned by the method. If I put a Thread.Sleep(5000) on the "// Wait here?" line it will complete, however.
How can I make this function to wait for the string download to complete and return as soon as it does without entering a static delay?
public string uriRead(string uri)
{
string result = "";
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(AsyncReadCompleted);
client.DownloadStringAsync(new Uri(uri));
// Wait here?
return result = downloadedAsyncText;
}
public void AsyncReadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
Console.WriteLine("Event Called");
downloadedAsyncText = e.Result.ToString();
Console.WriteLine(e.Result);
}
Sorry but as other mentioned if you are using Async you should use it properly.
The results are supposed to be read in the DownloadStringCompletedEventHandler, you should not wait, this can block your application. Your application need to stay responsive. What if the method never returns?
You need to create a private field in your class private string results_ that you set in the event handler.
If you want to wait for the result then you want to do it synchronously, not asynchronously as mentioned by others. So use the DownloadString method instead of DownloadStringAsync.
public string uriRead(string uri)
{
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;
return client.DownloadString(new Uri(uri));
}

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

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