How to await for QueryCompleted event?

2020-02-16 05:05发布

问题:

I created a small test application to get the Longitude and Latitude and to convert it to the actual address:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Device.Location;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Maps.Services;
using Microsoft.Phone.Shell;
using PhoneApp1.Resources;
using Windows.Devices.Geolocation;

namespace PhoneApp1
{
public partial class MainPage : PhoneApplicationPage
    {
    private GeoCoordinate Location;
    public ObservableCollection<string> Addresses { get; set; }

    // Constructor
    public MainPage()
        {
        InitializeComponent();

        // Sample code to localize the ApplicationBar
        //BuildLocalizedApplicationBar();
        }

    protected override async void OnNavigatedTo( NavigationEventArgs e )
        {
        await GetLocation();
        }

    public async Task GetLocation()
        {
        Location = await CoordinateConverter.GetLocation();

        ReverseGeoCoding.StartReverseGeoCoding( Location );

        //ReverseGeoCoding.done.WaitOne();

        string Address = ReverseGeoCoding.Address;
        }
    }

public static class ReverseGeoCoding
    {
    public static ObservableCollection<string> Addresses = new ObservableCollection< string >();
    public static string Address;
    public static bool Completed;
    public static AutoResetEvent done = new AutoResetEvent( true );

    public static void StartReverseGeoCoding( GeoCoordinate Location )
        {
        Completed = false;
        var reverseGeocode = new ReverseGeocodeQuery();
        reverseGeocode.GeoCoordinate = new GeoCoordinate( Location.Latitude, Location.Longitude );
        reverseGeocode.QueryCompleted += ReverseGeocodeQueryCompleted;
        done.Reset(); 
        reverseGeocode.QueryAsync();
        }

    public static void ReverseGeocodeQueryCompleted( object sender, QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>> e )
        {
        var reverseGeocode = sender as ReverseGeocodeQuery;
        if ( reverseGeocode != null )
            {
            reverseGeocode.QueryCompleted -= ReverseGeocodeQueryCompleted;
            }

        //Microsoft.Phone.Maps.Services.MapAddress address;                 
        Addresses.Clear();
        if ( !e.Cancelled )
            {
            foreach ( var address in e.Result.Select( adrInfo => adrInfo.Information.Address ) )
                {
                Addresses.Add( string.Format( "{0} {1}, {2} {3} {4}, {5}",
                  address.HouseNumber,
                  address.Street,
                  address.City,
                  address.State,
                  address.PostalCode,
                  address.Country ).Trim() );
                }
            }

        if ( Addresses.Count > 0 )
            {
            Address = Addresses[ 0 ].ToString();
            }
        else
            {
            Address = "";
            }

        done.Set(); 
        Completed = true;
        }
    }

public static class CoordinateConverter
    {
    public static GeoCoordinate ConvertGeocoordinate( Geocoordinate geocoordinate )
        {
        return new GeoCoordinate
            (
            geocoordinate.Latitude,
            geocoordinate.Longitude,
            geocoordinate.Altitude ?? Double.NaN,
            geocoordinate.Accuracy,
            geocoordinate.AltitudeAccuracy ?? Double.NaN,
            geocoordinate.Speed ?? Double.NaN,
            geocoordinate.Heading ?? Double.NaN
            );
        }

    public static async Task<GeoCoordinate> GetLocation()
        {
        // Get current location.
        Geolocator myGeolocator = new Geolocator();
        myGeolocator.DesiredAccuracy = PositionAccuracy.High;
        //myGeolocator.DesiredAccuracyInMeters = 50;

        Geocoordinate myGeocoordinate = null;

        try
            {
            Geoposition myGeoposition = await myGeolocator.GetGeopositionAsync
                    (
                    maximumAge: TimeSpan.FromMinutes( 1 ),
                    timeout: TimeSpan.FromSeconds( 10 )
                    );
            myGeocoordinate = myGeoposition.Coordinate;
            }
        catch ( Exception ex )
            {
            if ( (uint)ex.HResult == 0x80004004 )
                {
                // the application does not have the right capability or the location master switch is off
                MessageBox.Show( "location  is disabled in phone settings" );
                }
            }

        if ( myGeocoordinate == null )
            {
            return GeoCoordinate.Unknown;
            }

        GeoCoordinate myGeoCoordinate = CoordinateConverter.ConvertGeocoordinate( myGeocoordinate );
        return myGeoCoordinate;
        }
    }
}

The code works fine, i.e. the ReverseGeocodeQueryCompleted is called and the address is being calculated properly. However, ReverseGeocodeQueryCompleted occurs after GetLocation() is completed and the address assigned to Address is null.

My question is how to make

ReverseGeoCoding.StartReverseGeoCoding( Location ); 

wait for the completion of:

ReverseGeocodeQueryCompleted( object sender, QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>> e )
{
....
}

I tried with AutoResetEvent and WaitOne, but the whole thread stops and the code never gets to ReverseGeocodeQueryCompleted().

I am open to suggestions how to solve this problem.

EitanB

回答1:

Here is an extension method to be Able to await QueryAsync:

public static Task<IList<MapLocation>> QueryTaskAsync(this ReverseGeocodeQuery reverseGeocode)
{
    TaskCompletionSource<IList<MapLocation> > tcs = new TaskCompletionSource<IList<MapLocation>>();
    EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> queryCompleted = null;

    queryCompleted = (send, arg) =>
        {
            // Unregister event so that QueryTaskAsync can be called several time on same object
            reverseGeocode.QueryCompleted -= queryCompleted;

            if (arg.Error != null)
            {
                tcs.SetException(arg.Error);
            }
            else if (arg.Cancelled)
            {
                tcs.SetCanceled();
            }
            else
            {
                tcs.SetResult(arg.Result);
            }
        };

        reverseGeocode.QueryCompleted += queryCompleted;

        reverseGeocode.QueryAsync();

        return tcs.Task;
    }


回答2:

I modified a bit Benoit's answer to look like this:

        public static Task<string> StartReverseGeoCodingAsync( System.Device.Location.GeoCoordinate Location )
        {
        var reverseGeocode = new ReverseGeocodeQuery();
        reverseGeocode.GeoCoordinate = new System.Device.Location.GeoCoordinate( Location.Latitude, Location.Longitude );

        var tcs = new TaskCompletionSource<string>();
        EventHandler<QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>>> handler = null;
        handler = ( sender, args ) =>
        {
            if ( args.Error != null )
                {
                tcs.SetException( args.Error );
                }
            else if ( args.Cancelled )
                {
                tcs.SetCanceled();
                }
            else
                {
                Addresses.Clear();
                foreach ( var address in args.Result.Select( adrInfo => adrInfo.Information.Address ) )
                    {
                    Addresses.Add(
                        string.Format( "{0} {1}, {2} {3} {4}, {5}",
                                       address.HouseNumber,
                                       address.Street,
                                       address.City,
                                       address.State,
                                       address.PostalCode,
                                       address.Country ).Trim() );
                    }
                string Address = Addresses.Count > 0 ? Address = Addresses[ 0 ].ToString() : string.Empty;
                reverseGeocode.QueryCompleted -= handler;
                tcs.SetResult( Address );
                }
        };

        reverseGeocode.QueryCompleted += handler;
        reverseGeocode.QueryAsync();
        return tcs.Task;
        }

this will replace the following two functions in my code:

#if never
    public static void StartReverseGeoCoding( GeoCoordinate Location )
        {
        var reverseGeocode = new ReverseGeocodeQuery();
        reverseGeocode.GeoCoordinate = new GeoCoordinate( Location.Latitude, Location.Longitude );
        reverseGeocode.QueryCompleted += ReverseGeocodeQueryCompleted;
        reverseGeocode.QueryAsync();
        }

    public static void ReverseGeocodeQueryCompleted( object sender, QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>> e )
        {
        var reverseGeocode = sender as ReverseGeocodeQuery;
        if ( reverseGeocode != null )
            {
            reverseGeocode.QueryCompleted -= ReverseGeocodeQueryCompleted;
            }

        // Microsoft.Phone.Maps.Services.MapAddress address;                
        Addresses.Clear();
        if ( !e.Cancelled )
            {
            foreach ( var address in e.Result.Select( adrInfo => adrInfo.Information.Address ) )
                {
                Addresses.Add( string.Format( "{0} {1}, {2} {3} {4}, {5}",
                  address.HouseNumber,
                  address.Street,
                  address.City,
                  address.State,
                  address.PostalCode,
                  address.Country ).Trim() );
                }
            }

        Address = ( Addresses.Count > 0 ) ? Addresses[ 0 ].ToString() : string.Empty;
        }

endif

Overall works fine, and again thanks to Benoit!



回答3:

Look for TaskCompletionSource for task synchronization.

I'll write a better response later. Meanwhile, take a look at Bringing async/await to the Contacts service on the Nokia Developer Wiki.