Reputation: 6457
Can someone please put me in the right direction here, as I'm totally confused as to why when I call GetGeopositionAsync from my ViewModel, it hangs but when called from my view it works ok?
Here is the code that works:
private async void btnLocate_Click(object sender, RoutedEventArgs e)
{
if (this._geolocator.LocationStatus != PositionStatus.Disabled && this._geolocator.LocationStatus != PositionStatus.NotAvailable)
{
try
{
this._geolocator.DesiredAccuracyInMeters = 5;
Geoposition position = await this._geolocator.GetGeopositionAsync();
Geocoordinate geocoordinate = position.Coordinate;
this.mapWithMyLocation.Center = CoordinateConverter.ConvertGeocoordinate(geocoordinate);
}
catch (System.UnauthorizedAccessException)
{
throw;
}
catch (TaskCanceledException)
{
throw;
}
catch (Exception)
{
throw;
}
}
}
Here is the code that doesn't work:
public class LocationEntryViewModel : BaseViewModel
{
async public Task<GeoCoordinate> GetMyLocation()
{
if (this._geolocator.LocationStatus != PositionStatus.Disabled && this._geolocator.LocationStatus != PositionStatus.NotAvailable)
{
try
{
this._geolocator.DesiredAccuracyInMeters = 5;
Geoposition position = await this._geolocator.GetGeopositionAsync();
Geocoordinate geocoordinate = position.Coordinate;
return CoordinateConverter.ConvertGeocoordinate(geocoordinate);
}
catch (System.UnauthorizedAccessException)
{
throw;
}
catch (TaskCanceledException)
{
throw;
}
catch (Exception)
{
throw;
}
}
else
{
return new GeoCoordinate();
}
}
public void SetMyLocation()
{
this.MyLocation = GetMyLocation().Result;
}
}
And then I call this from my View
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
_locationEntryViewModel.SetMyLocation();
}
I've read in an article, which I can no longer find, that I should not initialize this from the constructor of my ViewModel, so for testing purpose I put in in OnNavigateTo instead but if there is a way to do this, please let me know as I feel it would make more sense, no??
Thanks.
Upvotes: 0
Views: 586
Reputation: 10620
The .Result call is causing deadlock because the GetMyLocation method wants to continue running on UI thread at the same time, when you are waiting for the Result on UI thread.
For solving this just change the SetMyLocation method to be async and use it like this:
public async Task SetMyLocation()
{
this.MyLocation = await GetMyLocation();
}
And on the place, where you are calling SetMyLocation, use await as well.
Upvotes: 1
Reputation: 456352
Your problem is that you are calling Result
in the UI thread, thus causing a deadlock that I explain in full on my blog.
To fix this, you should use a type like the NotifyTaskCompletion
type in my AsyncEx library to provide property change events when tasks complete. It's fine to kick off this kind of initialization in your constructor.
public class LocationEntryViewModel : BaseViewModel
{
public INotifyTaskCompletion<GeoCoordinate> MyLocation { get; private set; }
public LocationEntryViewModel()
{
MyLocation = NotifyTaskCompletion.Create(GetMyLocation());
}
}
Then your data binding can use paths like MyLocation.Result
. Note that this is null
until GetMyLocation
completes. If you want a different UI (e.g., spinner or whatnot), you can data bind to MyLocation.IsCompleted
. Also, if you want to data bind your exceptions, you can use MyLocation.ErrorMessage
. Other convenience properties are available.
For more information, see my async
properties blog post.
Upvotes: 0