等待GetGeopositionAsync()的结果

时间:2013-12-30 18:31:18

标签: c# .net geolocation task-parallel-library async-await

我正在开发一个WinPhone 8应用程序。 在这个应用程序上有一个按钮'发送短信'。 当用户点击此按钮时,应该发生两件事:

  1. (方法A)获取当前位置的地理坐标(使用Geolocator和GetGeopositionAsync)。
  2. (方法B)编写并发送带有地理坐标的SMS作为正文的一部分。
  3. 问题:GetGeopositionAsync是一种异步方法。在检测到坐标(需要几秒钟)之前发送SMS(当然没有坐标)。

    如何告诉方法2等到坐标可用?

    好的,这是我的代码:

    当用户按下按钮时,坐标由第一种方法确定,第二种方法发送包含其体内坐标的SMS:

    private void btnSendSms_Click(object sender, RoutedEventArgs e)
    {
        GetCurrentCoordinate();  // Method 1
        // -> Gets the coordinates
    
        SendSms();               // Method 2
        // Sends the coordinates within the body text
    }
    

    第一种方法GetCurrentCoordinate()如下所示:

            ...
    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 + ")";
    
                }
            }
        }
    }
    

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

    问题是,当SendSms()方法设置SmsComposeTask对象时,所有这些TextBox(lblLatitude,lblLongitude,lblCurrAddress)仍为空。

    我必须确保在SendSms()方法开始之前已经设置了TextBoxes。

3 个答案:

答案 0 :(得分:3)

您应该永远标记方法async void,除非它是UI事件处理程序。您正在调用异步方法而不等待它结束。你基本上是并行调用这两个方法,所以很清楚为什么坐标不可用。

你需要让GetCurrentCoordinate返回一个等待它的任务并等待它,如下所示:

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

答案 1 :(得分:1)

这是您应该避免async void的主要原因之一。对于void方法,async是一种非常不自然的返回类型。

首先,使用GetCurrentCoordinate async Task方法而不是async void。然后,您可以将单击处理程序更改为:

private async void btnSendSms_Click(object sender, RoutedEventArgs e)
{
  await GetCurrentCoordinate();
  SendSms();
}

您的点击处理程序仅为async void,因为事件处理程序才能返回void。但是你应该努力避免在所有其他代码中使用async void

答案 2 :(得分:0)

这里有两件事你错了:

  1. 当您需要等待它们时,使用void返回async方法。这很糟糕,因为您无法等待执行这些方法,只有在无法使方法返回TaskTask<T>时才能使用。这就是为什么在调用SendSms时你没有在文本框上看到任何内容。
  2. 混合UI和非UI代码。您应该在UI和非UI代码之间传输数据,以避免具有不同职责的代码之间的紧密耦合。 IT还使代码的读取和调试变得容易。
  3. ReverseGeocodeQuery没有等待的异步API,只有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;
        }
    }
    

    这样您就可以获得完全取消和错误支持。

    现在让我们在一个块中检索地理坐标信息:

    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);
        }
    }
    

    现在按钮eventnt处理程序变得更具可读性:

    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);
    }
    

    这有意义吗?