【问题标题】:Waiting on the results of GetGeopositionAsync()等待 GetGeopositionAsync() 的结果
【发布时间】:2013-12-30 18:31:18
【问题描述】:

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

  1. (方法 A)获取当前位置的地理坐标(使用 Geolocator 和 GetGeopositionAsync)。
  2. (方法 B)编写并发送带有地理坐标的 SMS 作为正文的一部分。

问题:GetGeopositionAsync 是一种异步方法。在检测到坐标之前(需要几秒钟),会发送 SMS(当然没有坐标)。

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

好的,这是我的代码:

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

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

            }
        }
    }
}

以及方法“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 对象时,所有这些文本框(lblLatitude、lblLongitude、lblCurrAddress)仍然是空的。

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

【问题讨论】:

  • 你应该尝试使用代码自己解决它

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


【解决方案1】:

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

你需要让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();
}

【讨论】:

    【解决方案2】:

    这是您应该避免使用async void 的主要原因之一。 voidasync 方法的一个非常不自然的返回类型。

    首先,将GetCurrentCoordinate 设为async Task 方法,而不是async void。然后,您可以将点击处理程序更改为如下所示:

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

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

    【讨论】:

    • 非常感谢。就是这样。
    【解决方案3】:

    你在这里做错了两件事:

    1. 当您需要等待它们时,使用 void 返回 async 方法。这很糟糕,因为您不能等待这些方法的执行,并且只能在无法使方法返回TaskTask&lt;T&gt; 时使用。这就是为什么在调用 SendSms 时您在文本框中看不到任何内容的原因。
    2. 混合 UI 和非 UI 代码。您应该在 UI 和非 UI 代码之间传输数据,以避免具有不同职责的代码之间的紧密耦合。 IT 还可以轻松阅读和调试代码。

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

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

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

    这有意义吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-30
      • 2017-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多