uwp async method skipping code lines - c#

I have an async method with void return type. Here it is
public static async void LoadPlaylists()
{
if(playlistitems.Count==0)
{
var playlists = await ApplicationData.Current.LocalFolder.GetFilesAsync();
var c = playlists.Count;
foreach (var playlist in playlists)
{
var p = await Playlist.LoadAsync(playlist);
var image = new BitmapImage();
if (p.Files.Count == 0)
{
image.UriSource = new Uri("ms-appx:///Assets/Wide310x150Logo.scale-200.png");
}
else
{
image = await Thumbnail(p.Files[0]);
}
playlistitems.Add
(
new PlaylistItem
{
playlist = p,
PlaylistName = playlist.DisplayName,
NumOfVid = p.Files.Count.ToString(),
Thumbnail = image
}
);
}
}
}
It is a public static method so I can use it anywhere, I use it on one page to load some data and it works fine,and it completes this method and then move forward on next code line. as shown below
private void l1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//some more code
LoadPlaylists();
//some more code
}
But when I use it on another page in another event handler, which is not async method, it just runs the first line, and then it skips whole method and moves forward. I know for sure that it is skipping those lines, bcz i checked with break point, I know it is skipping because it is async but I dnt want that, I just want it to complete the whole method and then move forward. S o that I dont get any problem on next code lines. I am pasting the code below again, to show you with comments what it skipps.
public static async void LoadPlaylists()
{
if(playlistitems.Count==0)
{
//it runs till here, when compiler goes to line below
//it skips whole methods and exits it.
var playlists = await ApplicationData.Current.LocalFolder.GetFilesAsync();
var c = playlists.Count;
foreach (var playlist in playlists)
{
var p = await Playlist.LoadAsync(playlist);
var image = new BitmapImage();
if (p.Files.Count == 0)
{
image.UriSource = new Uri("ms-appx:///Assets/Wide310x150Logo.scale-200.png");
}
else
{
image = await Thumbnail(p.Files[0]);
}
playlistitems.Add
(
new PlaylistItem
{
playlist = p,
PlaylistName = playlist.DisplayName,
NumOfVid = p.Files.Count.ToString(),
Thumbnail = image
}
);
}
}
}
Now below is the code where I am using it in an async event handler, and whre it is causing me problem.
private async void ME_MediaOpened(object sender, RoutedEventArgs e)
{
//When used here, it just skippes everything in the method
//as i described on the comments in code above.
LoadPlaylists();
//after skipping the compiler comes here and tries to execute
//the lines below, which obviously causes exceptions because
above method was never completed to begin with.
var sd = playlistitems.Count;
//some more code
}

You should return the Task instead of void so that you can await for the method to complete.

Related

Why does the Dropbox API's "DownloadAsync" method freeze when run in an async Task<bool> method. C#

So, I use the Dropbox API in my C# application to download files and check strings. Previously, I've always used the "DownloadAsync" method to download a file and get it's contents in an "async void" method. Recently, however, I also needed to return a boolean, so I put the same code that I've always used in an "async Task" method, so that I could check the result. However, when I do this, the "DownloadAsync" method never finishes (runs forever). If I take the same method and put it in an "async void" method (without returning a result) the "DownloadAsync" method finishes just fine. I'm not sure why this makes a difference at all, but it does.
In this first method, the "keysToCheck" string gets set, boolean gets set, and method finishes.
private async void SetStringAndBoolean(string text)
{
var keysToCheck= "";
bool booleanToCheck;
using (var dbx = new DropboxClient("TOKEN"))
{
using (var response = await dbx.Files.DownloadAsync("/FileToDownload.txt"))
{
keysToCheck = await response.GetContentAsStringAsync();
}
}
var keys = keysToCheck.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList();
foreach (var key in keys)
if (key == text)
booleanToCheck = true;
booleanToCheck = false;
}
However, in the following method, the "DownloadAsync" method never finishes. It just runs forever. Any ideas what I'm doing wrong?
private async Task<bool> SetStringAndReturnBoolean(string text)
{
var keysToCheck = "";
using (var dbx = new DropboxClient("TOKEN"))
{
using (var response = await dbx.Files.DownloadAsync("/FileToDownload.txt"))
{
keysToCheck = await response.GetContentAsStringAsync();
}
}
var keys = keysToCheck.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList();
foreach (var key in keys)
if (key == text)
return true;
return false;
}
Calling Method (Button Click Event):
private async void Button_Click(object sender, RoutedEventArgs e)
{
if (!SetStringAndReturnBoolean(GuiTextBox.Text).Result)
ErrorRun.Text = "Invalid Key";
else
{
DialogResult = true;
Close();
}
}
Use await to call the method instead of accessing Result of the returned Task.
private async void Button_Click(object sender, RoutedEventArgs e)
{
bool result = await SetStringAndReturnBoolean(GuiTextBox.Text);
if (!result)
{
ErrorRun.Text = "Invalid Key";
}
else
{
DialogResult = true;
Close();
}
}
Async only works when you use it consistently. Never mix await and Task.Result or Task.Wait() unless you know very well what you do.

c# wrapping a Task into a another Task

I am trying to use inter process communication to send from one instance of my program its content to the other instance. The code I have under here is working but it makes the first instance freeze and only continues running when the second instansce is created and the data is passed back.I suspect its because messaging_server0() is not a async task. How would one approach this problem. Is there a way to make messaging_server0 async? or am I missing something?
main loop contains this piece of code
var makeTask = Task<string>.Factory.StartNew(() => pipe_server.messaging_server0());
if (makeTask.Result != null) {
dataGrid_logic.DataGridLoadTarget(makeTask.Result);
}
and on the other side I have messaging_server0
public static string messaging_server0()
{
using (var messagebus1 = new TinyMessageBus("ExampleChannel"))
{
string ret = null;
messagebus1.MessageReceived += (sender, e) => ret = Encoding.UTF8.GetString(e.Message);
while (true)
{
if (ret != null)
{
return ret;
}
}
}
}
The method names are going to be refactored.
I would suggest following approach:
public static async Task<string> messaging_server0()
{
using (var messagebus1 = new TinyMessageBus("ExampleChannel"))
{
var taskCompletition = new TaskCompletionSource<string>();
messagebus1.MessageReceived +=
(sender, e) =>
{
var ret = Encoding.UTF8.GetString(e.Message);
taskCompletition.TrySetResult(ret);
};
return await taskCompletition.Task;
}
}
Obviously you would need to add some error handling, time outs if needed etc.
with the help of alek kowalczyk and some digging I came up with this code
private async void xxx()
{
var makeTask = Task<string>.Factory.StartNew(() => pipe_server.messaging_server());
await pipe_server.messaging_server();
{
dataGrid_logic.DataGridLoadTarget(makeTask.Result);
}
}
plus the snippet he posted.
This works perfectly. thanks

Xamarin Forms Play a sound Asynchronously

I can successfully play sounds using Xamarin forms (Android and iOS) however I also need to achieve the following:
I need to await so that if multiple sounds are 'played', one will complete before the next.
I need to return a boolean to indicate whether operation was a success.
Here is my current simplified code (for the iOS platform):
public Task<bool> PlayAudioTask(string fileName)
{
var tcs = new TaskCompletionSource<bool>();
string filePath = NSBundle.MainBundle.PathForResource(
Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
var url = NSUrl.FromString(filePath);
var _player = AVAudioPlayer.FromUrl(url);
_player.FinishedPlaying += (object sender, AVStatusEventArgs e) =>
{
_player = null;
tcs.SetResult(true);
};
_player.Play();
return tcs.Task;
}
To test the method, I have tried calling it like so:
var res1 = await _audioService.PlayAudioTask("file1");
var res2 = await _audioService.PlayAudioTask("file2");
var res3 = await _audioService.PlayAudioTask("file3");
I had hoped to hear the audio for file1, then file2, then file3. However I only hear file 1 and the code doesn't seem to reach the second await.
Thankyou
I think your issue here is that the AVAudioPlayer _player was being cleared out before it was finished. If you were to add debugging to your FinsihedPlaying, you'll notice that you never hit that point.
Try these changes out, I made a private AVAudioPlayer to sit outside of the Task
(I used the following guide as a reference https://developer.xamarin.com/recipes/ios/media/sound/avaudioplayer/)
public async void play()
{
System.Diagnostics.Debug.WriteLine("Play 1");
await PlayAudioTask("wave2.wav");
System.Diagnostics.Debug.WriteLine("Play 2");
await PlayAudioTask("wave2.wav");
System.Diagnostics.Debug.WriteLine("Play 3");
await PlayAudioTask("wave2.wav");
}
private AVAudioPlayer player; // Leave the player outside the Task
public Task<bool> PlayAudioTask(string fileName)
{
var tcs = new TaskCompletionSource<bool>();
// Any existing sound playing?
if (player != null)
{
//Stop and dispose of any sound
player.Stop();
player.Dispose();
}
string filePath = NSBundle.MainBundle.PathForResource(
Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
var url = NSUrl.FromString(filePath);
player = AVAudioPlayer.FromUrl(url);
player.FinishedPlaying += (object sender, AVStatusEventArgs e) =>
{
System.Diagnostics.Debug.WriteLine("DONE PLAYING");
player = null;
tcs.SetResult(true);
};
player.NumberOfLoops = 0;
System.Diagnostics.Debug.WriteLine("Start Playing");
player.Play();
return tcs.Task;
}

attempted to read write protected memory when adding to list

I have a code which should take all pictures from folder, put them into object named "PhotoInspection", add some informations and put this object into list. See the code below
private async void btnSend_Click(object sender, RoutedEventArgs e)
{
bool isOn = tsOnOff.IsOn;
TextBlock tbChosen = new TextBlock();
tbChosen = lbInspections.SelectedItem as TextBlock;
string chosen = tbChosen.Text;
AllInspectionPhotos aip = new AllInspectionPhotos();
var folders = await ApplicationData.Current.LocalFolder.GetFoldersAsync();
if (isOn)
{
foreach (var item in folders)
{
if (item.Name == "ONLINE")
{
var inspectionFolders = await item.GetFoldersAsync();
foreach (var inspectionFolder in inspectionFolders)
{
if (inspectionFolder.Name == chosen)
{
aip.InspectionEan = chosen;
var photos = await inspectionFolder.GetFilesAsync();
foreach (var photo in photos)
{
using (Stream stream = await photo.OpenStreamForReadAsync())
{
PhotoInspection phtInsp = new PhotoInspection();
var bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
phtInsp.Photo = bytes;
phtInsp.InspectionEan = chosen;
phtInsp.PhotoName = photo.Name;
aip.Photos.Add(phtInsp);
}
}
}
}
}
}
}
else
{
foreach (var item in folders)
{
if (item.Name == "OFFLINE")
{
var inspectionFolders = await item.GetFoldersAsync();
foreach (var inspectionFolder in inspectionFolders)
{
if (inspectionFolder.Name == chosen)
{
aip.InspectionEan = chosen;
var photoset = await inspectionFolder.GetFilesAsync();
foreach (var photo in photoset)
{
PhotoInspection phtInsp = new PhotoInspection();
using (Stream stream = await photo.OpenStreamForReadAsync())
{
var bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
phtInsp.Photo = bytes;
phtInsp.InspectionEan = chosen;
phtInsp.PhotoName = photo.Name;
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//await App._client.SendPhotoAsync(phtInsp);
aip.Photos.Add(phtInsp); //fires error here
});
}
}
}
}
}
}
await App._client.SendAllPhotosAsync(aip);
}
However when I try to add object into list, I get "attempted to read write protected memory" error. PhotoInspection object is filled with relevant data and everything looks good before adding to list. Thanks for any help
The problem is due to the Dispatcher.RunAsync call, which returns control to the calling routine as soon as the aip.Photos.Add() lambda is queued, and not when it has completed. This results in the aip object being disposed before the lambda has actually run.
The MSDN page for CoreDispatcher.RunAsync says:
[...] it schedules the work on the UI thread and returns control to the caller immediately.
and:
To spin off a worker thread from the UI thread, do not use this method (CoreDispatcher::RunAsync). Instead, use one of the Windows::System::Threading::ThreadPool::RunAsync method overloads.
It also provides the following code example, and the comment is from Microsoft!
//DO NOT USE THIS CODE.
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await signInDialog.ShowAsync();
});
// Execution continues here before the call to ShowAsync completes.

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?

Categories