CrossGeolocator GetPositionAsync exits loop/method - c#

I want to keep the phone's location quite often in an App, so I'm using the GetPositionAsync method, and keep calling that. I've written some code, and when I attach that in a simple OnAppearing method everything works fine. If I write it in a method or while loop, when it gets to the GetPositionAsync or some other local database things, I think only awaitable methods, it goes right out of the loop/method. I am using async. Any ideas?
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 20;
var position = await locator.GetPositionAsync(TimeSpan.FromSeconds(10));
location.Latitude = position.Latitude;
location.Longitude = position.Longitude;
await App.LocationDatabase.SaveLocationAsync(location);
await Task.Delay(TimeSpan.FromSeconds(10));

You're not sharing any loop code, so it's hard to reproduce your issue.
Anyway, you shouldn't call GetPositionAsync in a loop, use the StartListeningAsync method and connect to the PositionChange event instead.
async Task StartListeningAsync()
{
if(CrossGeolocator.Current.IsListening)
return;
await CrossGeolocator.Current.StartListeningAsync(TimeSpan.FromSeconds(5), 10, true);
CrossGeolocator.Current.PositionChanged += PositionChanged;
CrossGeolocator.Current.PositionError += PositionError;
}
private void PositionChanged(object sender, PositionEventArgs e)
{
//If updating the UI, ensure you invoke on main thread
var position = e.Position;
var output = "Full: Lat: " + position.Latitude + " Long: " + position.Longitude;
output += "\n" + $"Time: {position.Timestamp}";
output += "\n" + $"Heading: {position.Heading}";
output += "\n" + $"Speed: {position.Speed}";
output += "\n" + $"Accuracy: {position.Accuracy}";
output += "\n" + $"Altitude: {position.Altitude}";
output += "\n" + $"Altitude Accuracy: {position.AltitudeAccuracy}";
Debug.WriteLine(output);
}
private void PositionError(object sender, PositionErrorEventArgs e)
{
Debug.WriteLine(e.Error);
//Handle event here for errors
}
async Task StopListeningAsync()
{
if(!CrossGeolocator.Current.IsListening)
return;
await CrossGeolocator.Current.StopListeningAsync();
CrossGeolocator.Current.PositionChanged -= PositionChanged;
CrossGeolocator.Current.PositionError -= PositionError;
}
https://jamesmontemagno.github.io/GeolocatorPlugin/LocationChanges.html

Related

Asynchronous function call of Windows Forms C#

I have an asynchronous function. It is called only once when the form is displayed for the first time. My function should ping devices asynchronously when I open the program. But it turns out that when you close the child form, another poll is launched. Tell me where the error may be.
Function call (I tried to call it in formLoad):
private async void MainForm_Shown(object sender, EventArgs e)
{
await Start();
}
Function itself:
public async Task Start()
{
while (keyOprosDev)
{
for (int i = 0; i < devicesListActivity.Count; i++)
{
devicesListActivity[i].DevicesList.DevicesTotalPing++;
string ipAdresDevice = devicesListActivity[i].DevicesList.DevicesName;
int portDevice = devicesListActivity[i].DevicesList.DevicesPort;
int activeDevice = devicesListActivity[i].DevicesList.DevicesActiv;
int sendTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeSend;
int respTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeResp;
using (TcpClient client = new TcpClient())
{
if (activeDevice == 1)
{
client.SendTimeout = sendTimeDevice;
client.ReceiveTimeout = respTimeDevice;
var ca = client.ConnectAsync(ipAdresDevice, portDevice);
await Task.WhenAny(ca, Task.Delay(sendTimeDevice));
client.Close();
if (ca.IsFaulted || !ca.IsCompleted)
{
textBox1.AppendText($"{DateTime.Now.ToString()} Server refused connection." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 1;
}
else
{
devicesListActivity[i].DevicesList.DevicesSuccessPing++;
textBox1.AppendText($"{DateTime.Now.ToString()} Server available" + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 2;
}
}
else
{
}
}
await Task.Delay(interval);
}
}
}
And here is the opening of the child form:
try
{
DbViewer dbViewer = new DbViewer();
dbViewer.FormClosed += new FormClosedEventHandler(refr_FormClosed);
dbViewer.ShowDialog();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
This is the event that handles the closure of the child form:
void refr_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
kryptonTreeView1.Nodes[0].Nodes[0].Nodes.Clear();
kryptonTreeView1.Nodes[0].Nodes[1].Nodes.Clear();
loadIpListFromDb();
loadComListFromDb();
kryptonTreeView1.ExpandAll();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
}
You need to pass a cancellation token in. Somewhere outside of this code you need to create a CancellationTokenSource the best place is probably an property of the form:
class MainForm
{
CancellationTokenSource cts;
...
You then initialize this and pass this to Start():
private async void MainForm_Shown(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
await Start(ct);
}
In your start loop you need to monitor for the cancellation token:
Because you're using a delay to timeout the ConnectAsync() you need the Task.Delay() to know when cancellation is requested so you need to pass the token to Task.Delay():
await Task.WhenAny(ca, Task.Delay(sendTimeDevice,ct));
After the TcpClient.Close() you need to test if the cancellation is requested, and stop loops if it is:
if (ct.IsCancellationRequested)
break;
You'll need to perform the same test in the while loop, and also you should perform it immediately before the ConnectAsync(). While the most likely place you will encounter ct.IsCancellationRequested == true will be immediately after the Task.WhenyAny or immediately after the Loop interval there's no point starting a ConnectAsync() if cancellation has been requested.
You should also pass the CancellationToken to the Loop interval, otherwise you could end up waiting interval before your form closes:
// This will throw an OperationCancelled Exception if it is cancelled.
await Task.Delay(interval,ct);
Because you're going to continue anyway and just exit if Cancellation is registered you could avoid writing a separate try/catch that does nothing and await the interval like this, it's almost certainly less efficient, but it's cleaner.
// Leave any exceptions of Task.Delay() unobserved and continue
await Task.WhenAny(Task.Delay(interval,ct));
Finally you need to dispose of the CancellationTokenSource, I guess you would do this in something like a MainForm_Closed() function?
private void MainForm_Closed(object sender, EventArgs e)
{
cts.Dispose();
The only thing left to do is to work out when you want to fire the CancellationRequest, based on what you have said you want to do this when the form close button has been clicked, so:
private void MainForm_Closing(object sender, EventArgs e)
{
cts.Cancel();
That will cause the CancellationToken to transition to a cancelled state and your Start() routine will see that and exit.
In your code there is no single place to check for the CancellationToken being set, the rule of thumb is to check for it before and after any await and in your case you should check for it in both the while and the for loop.

C# Multiple Ping start and overlap their Results

I have created a code that will continously test the if the connection is available and is supposed to be able to change the IP and always needs to run so it can show in a notificion if the connection is available or not. I can execute it with no errors but when I click the send button again it starts another ping that will also put the results in the label box overlapping each other. I am open to all sugestions to fixing this.
private async void Button1_Click(object sender, EventArgs e) //send button
{
int unterbrechung = 0;
await Task.Delay(12);
//Erstellen Variablen
string value = (comboBox1.SelectedItem as dynamic).Value;
double laufzeit = 0;
double delayins = 0;
int delay = 1000;
int onetimebubble = 1; //Zum verhindern von wieder erscheinen der Warnmeldung
//Zum verhindern das MiniIcon ein weiterers mal zu ändern.
int onetimeminired = 0;
int onetimeminigreen = 0;
//Ping Variablen deklarieren und setzen
Ping pingSender = new Ping();
PingOptions options = new PingOptions
{
DontFragment = true
};
pingSender.SendAsyncCancel();
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
unterbrechung = 1;
//Ping senden und bei fehler entsprechende meldung anzeigen
while (unterbrechung == 1)
{
try
{
//Ping ausführen und Ausgabe meldung
PingReply reply = pingSender.Send(value, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
labelOutput.Text = "Address :" + reply.Address.ToString() + " " + Environment.NewLine +
"Status :" + reply.Status + " " + Environment.NewLine +
"Laufzeit in s :" + laufzeit;
labelOutput.BackColor = System.Drawing.Color.Green;
//Reset der Variable
if (onetimeminigreen == 0)
{
//ändern Symbol + vorheriger test
minIcon.Icon = Properties.Resources.Symbol2;
onetimeminigreen = 1;
onetimeminired = 0;
}
if (onetimebubble == 0)
{
onetimebubble = 1;
}
}
}
catch(PingException)
{
//Reset Laufzeit
laufzeit = 0;
//ausgabe fehlermeldung
labelOutput.Text = "Address :" + value + " " + Environment.NewLine +
"Status :Failed" + Environment.NewLine +
"Laufzeit in s :" + laufzeit;
labelOutput.BackColor = System.Drawing.Color.Red;
await Task.Delay(10);
if (onetimeminired == 0)
{
//ändern Symbol + vorheriger test
minIcon.Icon = Properties.Resources.Symbol1;
onetimeminigreen = 0;
onetimeminired = 1;
}
if (onetimebubble == 1)
{
//ausgabe Warnmeldung + vorheriger test
minIcon.BalloonTipIcon = ToolTipIcon.Error;
minIcon.BalloonTipTitle = "Ping Failed!";
minIcon.BalloonTipText = "Sie haben keine verbindung zu ihrem Host";
minIcon.ShowBalloonTip(1000);
onetimebubble = 0;
}
}
//Ausführen delay
await Task.Delay(delay);
//erechnen des Delay in sekunden und laufzeit berechnung
delayins = delay / 1000;
laufzeit = laufzeit + delayins;
}
}
EDIT Forgot the Question. How do I change my programm in the way that if I click the button again it cancels all other pings I started before. that "unterbrechung" was something I tried to achieve that but failed.
Encapsulate pinging
As Fildor suggested I added a new class for the Ping that simply returns the result
public bool Ping(string ipaddress)
{
Ping pingSender = new Ping();
PingOptions options = new PingOptions
{
DontFragment = true
};
int timeout = 120;
pingSender.SendAsyncCancel();
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
try
{
PingReply reply = pingSender.Send(ipaddress, timeout, buffer, options);
if(reply.Status == IPStatus.Success)
{
return true;
}
else
{
return false;
}
}
catch (PingException)
{
return false;
}
}
METHOD PING AND UI CHANGES As Fildor also suggested I now added a method that will execute the ping and also the change the UI according to the Pings Result
private void ExecPingAndChangeUI()
{
runtime = 0; //reset runtime
delayins = interval / 1000; // umwandeln in Sekunden
if (comboBox1.SelectedItem != null) //Überprüfen ob ein Element in der Combobox ausgewählt wurde
{
value = (comboBox1.SelectedItem as dynamic).Value; //Auslesen der Combobox
status = Ping(value); //Auslesen des Ping ergebnisses
if (status == true)
{
//Ausgabe an OutputLabel
labelOutput.Text = "Address : " + Environment.NewLine + value + Environment.NewLine + Environment.NewLine +
"Status : " + Environment.NewLine + "Success" + Environment.NewLine + Environment.NewLine +
"Laufzeit in s: " + Environment.NewLine + runtime + "s";
labelOutput.BackColor = System.Drawing.Color.Green;
if (minIcon.Icon != Properties.Resources.Symbol2) //ändern Symbol + Test ob symbol schon das ausgewählte ist
{
minIcon.Icon = Properties.Resources.Symbol2;
}
if (onetimebubble == false)
{
onetimebubble = true;
}
runtime = runtime + delayins; //laufzeit berechnung
}
else if (status == false)
{
runtime = 0; //Reset Laufzeit
//Ausgabe an OutputLabel
labelOutput.Text = "Address : " + Environment.NewLine + value + Environment.NewLine + Environment.NewLine +
"Status : " + Environment.NewLine + "Failed" + Environment.NewLine + Environment.NewLine +
"Laufzeit in s: " + Environment.NewLine + runtime + "s";
labelOutput.BackColor = System.Drawing.Color.Red;
if (minIcon.Icon != Properties.Resources.Symbol1) //ändern Symbol + vorheriger test
{
minIcon.Icon = Properties.Resources.Symbol1;
}
if (onetimebubble == true) //ausgabe Warnmeldung + vorheriger test
{
minIcon.ShowBalloonTip(1000);
onetimebubble = false;
}
runtime = runtime + delayins; //laufzeit berechnung
}
}
else
{
labelOutput.Text = "Bitte ein Element auswählen!"; //falls nichts in der Combobox ausgewählt wurde
}
}
I would like to recommend a "little" refactoring of that code:
Encapsulate pinging
Write a class that has only one purpose: Send a ping and return the result.
If the Ping class is a high enough abstraction for you, I would make an instance a private class property, that will be configured by the method of point 1 in next paragraph.
Introduce a Timer
Since you want to do recurring ping, it seems reasonable to use a Timer for that. This implies following steps:
Have a method that uses above mentioned ping class to execute the ping and updates the UI according to the delivered result. It should gather all needed information to execute the ping ( target, data ... ) so you can change ping target on the fly and it will be reflected accordingly when the next tick fires.
Change the contents of the button_click handler and the containing class as such:
2.1. have a property of type Timer. (default = null)
2.2. on button click:
2.2.1. check if timer property is not null:
True - stop timer and null property ( = stop pinging ),
False - go on
2.2.2. Create a Timer , set timer property to reference that and start it. The timer's tick shall execute the method of point 1.
Remarks
You may want to have a second button to explicitly stop pinging without starting a new ping. Hint: You could use a ToggleButton and use its state information to decide whether to start or stop pinging.
Note: The timer may execute it's tick handler (method from point 1.) on a separate thread depending on what timer implementation you use. So you may need to marshal UI-Updates to the UI thread. There are several possible methods to do that. I advise you to do the research yourself, as you stated you are a novice coder. You will learn a lot in the course.
Also, you may want to make sure that when the timer fires a tick, the last ping has returned (so they won't overlap).
Note: In order to improve this answer, I will add some references for you to start with later, when I'm at home.
Timer: see https://msdn.microsoft.com/en-us/library/system.windows.forms.timer(v=vs.110).aspx (Code example inside)
You can add a line of code at the beginning and at the end of your method to enable/disable
private async void Button1_Click(object sender, EventArgs e)
{
Button1.Enabled = false;
....
....
....
Button1.Enabled = true;
}
This should work fine with async/await.

Error: 'System.UnauthorizedAccessException' occurred in mscorlib.ni.dll

I'm writing a UWP C# app for my Raspberry Pi. I want to create a continuous recognition session, but every time my program hits await recognizer.ContinuousRecognitionSession.StartAsync(); it fails due to this error.
I've tried adding lines to my manifest file such as <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> but to no avail.
How should I go about calling this method? I've set up prerequisites like a grammar.xml file for calling it, but I still can't find the issue:
const string SRGS_FILE = "Grammar\\grammar.xml";
SpeechRecognizer recognizer;
public MainPage()
{
this.InitializeComponent();
Unloaded += MainPage_Unloaded;
initializeSpeechRecognizer();
}
private async void initializeSpeechRecognizer()
{
// Initialize recognizer
recognizer = new SpeechRecognizer();
// Set event handlers
recognizer.StateChanged += RecognizerStateChanged;
recognizer.ContinuousRecognitionSession.ResultGenerated += RecognizerResultGenerated;
txbSpeech.Text += "Event handlers set\n";
// Load Grammer file constraint
string fileName = String.Format(SRGS_FILE);
StorageFile grammarContentFile = await Package.Current.InstalledLocation.GetFileAsync(fileName);
txbSpeech.Text += "Grammar file constraint loaded\n";
SpeechRecognitionGrammarFileConstraint grammarConstraint = new SpeechRecognitionGrammarFileConstraint(grammarContentFile);
// Add to grammer constraint
recognizer.Constraints.Add(grammarConstraint);
txbSpeech.Text += "Grammar constraint added\n";
// Compile grammer
SpeechRecognitionCompilationResult compilationResult = await recognizer.CompileConstraintsAsync();
txbSpeech.Text += "Grammar compiled\n";
txbSpeech.Text += ("Status: " + compilationResult.Status.ToString() + "\n");
// If successful, display the recognition result.
if (compilationResult.Status == SpeechRecognitionResultStatus.Success)
{
txbSpeech.Text += ("Result: " + compilationResult.ToString()) + "\n";
// The line below fails
await recognizer.ContinuousRecognitionSession.StartAsync();
}
else
{
txbSpeech.Text += ("Status: " + compilationResult.Status + "\n");
}
}
private void RecognizerResultGenerated(SpeechContinuousRecognitionSession session, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
// Output debug strings
txbSpeech.Text += (args.Result.Status.ToString() + "\n");
txbSpeech.Text += (args.Result.Text + "\n");
}
// Recognizer state changed
private void RecognizerStateChanged(SpeechRecognizer sender, SpeechRecognizerStateChangedEventArgs args)
{
//txbSpeech.Text += ("Speech recognizer state: " + args.State.ToString() + "\n");
}

Waiting on the results of GetGeopositionAsync()

I'm developing a WinPhone 8 App.
On this App there is a Button 'Send SMS'.
When the user clicks on this button two things should happen:
(Method A) Get the geo-coordinate of the current Location (using Geolocator and GetGeopositionAsync).
(Method B) Compose and send an SMS with the geo-coordinate as part of the body.
The Problem: GetGeopositionAsync is an asynchronous method. Before the coordinate is detected (which takes a few seconds) the SMS is sent (of course with no coordinates).
How can I tell Method 2 to wait until the coordinates are available?
OK, here is my code:
When the user presses the button, the coordinates are determined by the first method and the second method sends the SMS which includes the coordinates in its body:
private void btnSendSms_Click(object sender, RoutedEventArgs e)
{
GetCurrentCoordinate(); // Method 1
// -> Gets the coordinates
SendSms(); // Method 2
// Sends the coordinates within the body text
}
The first method GetCurrentCoordinate() looks as follows:
...
private GeoCoordinate MyCoordinate = null;
private ReverseGeocodeQuery MyReverseGeocodeQuery = null;
private double _accuracy = 0.0;
...
private async void GetCurrentCoordinate()
{
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
try
{
Geoposition currentPosition = await geolocator.GetGeopositionAsync(
TimeSpan.FromMinutes(1),
TimeSpan.FromSeconds(10));
lblLatitude.Text = currentPosition.Coordinate.Latitude.ToString("0.000");
lblLongitude.Text = currentPosition.Coordinate.Longitude.ToString("0.000");
_accuracy = currentPosition.Coordinate.Accuracy;
MyCoordinate = new GeoCoordinate(
currentPosition.Coordinate.Latitude,
currentPosition.Coordinate.Longitude);
if (MyReverseGeocodeQuery == null || !MyReverseGeocodeQuery.IsBusy)
{
MyReverseGeocodeQuery = new ReverseGeocodeQuery();
MyReverseGeocodeQuery.GeoCoordinate = new GeoCoordinate(
MyCoordinate.Latitude,
MyCoordinate.Longitude);
MyReverseGeocodeQuery.QueryCompleted += ReverseGeocodeQuery_QueryCompleted;
MyReverseGeocodeQuery.QueryAsync();
}
}
catch (Exception)
{ // Do something }
}
private void ReverseGeocodeQuery_QueryCompleted(object sender,
QueryCompletedEventArgs<IList<MapLocation>> e)
{
if (e.Error == null)
{
if (e.Result.Count > 0)
{
MapAddress address = e.Result[0].Information.Address;
lblCurrAddress.Text = address.Street + " " + address.HouseNumber + ",\r" +
address.PostalCode + " " + address.City + ",\r" +
address.Country + " (" + address.CountryCode + ")";
}
}
}
}
And the Methode 'SendSms()':
private void SendSms()
{
SmsComposeTask smsComposeTask = new SmsComposeTask();
smsComposeTask.To = "0123456";
smsComposeTask.Body = "Current position: \rLat = " + lblLatitude.Text +
", Long = " + lblLongitude.Text +
"\r" + lblCurrAddress.Text;
// -> The TextBoxes are still empty!
smsComposeTask.Show();
}
The problem is, that all these TextBoxes (lblLatitude, lblLongitude, lblCurrAddress) are still empty when the method SendSms() sets the SmsComposeTask object.
I have to ensure that the TextBoxes are already set BEFORE the method SendSms() starts.
You should almost never mark a method async void unless it's a UI event handler. You're calling an asynchronous method without waiting for it to end. You are basically calling those 2 methods in parallel, so it's clear why the coordinates aren't available.
You need to make GetCurrentCoordinate return an awaitable task and await it, like this:
private async Task GetCurrentCoordinateAsync()
{
//....
}
private async void btnSendSms_Click(object sender, RoutedEventArgs e)
{
await GetCurrentCoordinateAsync();
// You'll get here only after the first method finished asynchronously.
SendSms();
}
This is one of the primary reasons you should avoid async void. void is a very unnatural return type for async methods.
First, make your GetCurrentCoordinate an async Task method instead of async void. Then, you can change your click handler to look like this:
private async void btnSendSms_Click(object sender, RoutedEventArgs e)
{
await GetCurrentCoordinate();
SendSms();
}
Your click handler is async void only because event handlers have to return void. But you should really strive to avoid async void in all other code.
There two things you're doing wrong here:
Using void returning async methods when you need to await on them. This is bad because you can't await on execution of these methods and should only be used when you can't make the method return Task or Task<T>. That's why you're not seeing anything on the text boxes when SendSmsis called.
Mixing UI and non-UI code. You should transfer data between UI and non-UI code to avoid tight coupling between code with different responsibilities. IT also makes it easy to read and debug the code.
ReverseGeocodeQuery does not have an awaitable async API but you can easily make your own:
private async Task<IList<MapLocation>> ReverseGeocodeQueryAsync(GeoCoordinate geoCoordinate)
{
var tcs = new TaskCompletionSource<IList<MapLocation>>();
EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> handler =
(s, e) =>
{
if (e.Cacelled)
{
tcs.TrySetCancelled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Result);
}
};
var query = new ReverseGeocodeQuery{ GeoCoordinate = geoCoordinate };
try
{
query.QueryCompleted += handler;
query.QueryAsync();
return await tcs.Task;
}
finally
{
query.QueryCompleted -= handler;
}
}
This way you'll get full cancellation and error support.
Now let's make the retrieval of the geo coordinate information all in one chunk:
private async Task<Tuple<Geocoordinate, MapLocation>> GetCurrentCoordinateAsync()
{
try
{
var geolocator = new Geolocator
{
DesiredAccuracy = PositionAccuracy.High
};
var currentPosition = await geolocator.GetGeopositionAsync(
TimeSpan.FromMinutes(1),
TimeSpan.FromSeconds(10))
.ConfigureAwait(continueOnCapturedContext: false);
var currentCoordinate = currentPosition.Coordinate;
var mapLocation = await this.ReverseGeocodeQueryAsync(
new GeoCoordinate(
currentCoordinate.Latitude,
currentCoordinate.Longitude));
return Tuple.Create(
currentCoordinate,
mapLocation.FirstOrDefault());
}
catch (Exception)
{
// Do something...
return Tuple.Create(null, null);
}
}
Now the button eventnt handler becomes much more readable:
private void btnSendSms_Click(object sender, RoutedEventArgs e)
{
var info = await GetCurrentCoordinate();
if (info.Item1 != nuil)
{
lblLatitude.Text = info.Item1.Latitude.ToString("0.000");
lblLongitude.Text = info.Item1.Longitude.ToString("0.000");
}
if (info.Item2 != null)
{
var address = info.Item2.Information.Address;
lblCurrAddress.Text = string.Format(
"{0} {1},\n{2} {3},\n{4} ({5})",
address.Street,
address.HouseNumber,
address.PostalCode,
address.City,
address.Country,
address.CountryCode);
}
SendSms(info.Item1, info.Item2);
}
Does this make sense?

Is there a smarter way than a busy-wait to check for download completion of System.Net.WebClient.DownloadFileAsync()?

I'm downloading a file using System.Net.WebClient.DownloadFileAsync(). The only reason for using the async version is to show the progress of the download.
My code execution may not continue before 100% have been reached. Currently I'm using a busy-wait (see code) but I wonder if there is a smarter way to do it.
using(WebClient oWebClient = new WebClient())
{
oWebClient.Encoding = System.Text.Encoding.UTF8;
long lReceived = 0;
long lTotal = 0;
// Set up a delegate to watch download progress.
oWebClient.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% (" + e.BytesReceived / 1024f + "kb of " + e.TotalBytesToReceive / 1024f + "kb)");
// Assign to outer variables to allow busy-wait to check.
lReceived = e.BytesReceived;
lTotal = e.TotalBytesToReceive;
};
oWebClient.DownloadFileAsync(new Uri(sUrl + "?" + sPostData), sTempFile);
while(lReceived == 0 || lReceived != lTotal)
{
// Busy wait.
Thread.Sleep(1000);
}
}
using(WebClient oWebClient = new WebClient())
{
// use an event for waiting, rather than a Thread.Sleep() loop.
var notifier = new AutoResetEvent(false);
oWebClient.Encoding = System.Text.Encoding.UTF8;
long lReceived = 0;
long lTotal = 0;
// Set up a delegate to watch download progress.
oWebClient.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% (" + e.BytesReceived / 1024f + "kb of " + e.TotalBytesToReceive / 1024f + "kb)");
// Assign to outer variables to allow busy-wait to check.
lReceived = e.BytesReceived;
lTotal = e.TotalBytesToReceive;
// Indicate that things are done
if(lReceived >= lTotal) notifier.Set();
};
oWebClient.DownloadFileAsync(new Uri(sUrl + "?" + sPostData), sTempFile);
// wait for signal to proceed
notifier.WaitOne();
}
Use an AutoResetEvent. Call its Set() method in a DownloadFileCompleted event handler, its WaitOne() method after the DownloadFileAsync() call.
using(WebClient oWebClient = new WebClient())
{
// use an event for waiting, rather than a Thread.Sleep() loop.
var notifier = new AutoResetEvent(false);
oWebClient.Encoding = System.Text.Encoding.UTF8;
long lReceived = 0;
long lTotal = 0;
// Set up a delegate to watch download progress.
oWebClient.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% (" + e.BytesReceived / 1024f + "kb of " + e.TotalBytesToReceive / 1024f + "kb)");
// Assign to outer variables to allow busy-wait to check.
lReceived = e.BytesReceived;
lTotal = e.TotalBytesToReceive;
};
// Set a delegate to watch for when the download is complete
oWebClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
// Indicate that things are done
notifier.Set();
};
oWebClient.DownloadFileAsync(new Uri(sUrl + "?" + sPostData), sTempFile);
// wait for signal to proceed
notifier.WaitOne();
}
I have expanded on #OJ's answer to instead set the notifier when the OpenReadCompleted fires. This will stop the thread from hanging if the file errors during download.
Ref: WebClient DownloadFileAsync hangs

Categories