我的后台代理中的反向地理编码导致我的实时磁贴不会更新,因为代理调用的其余代码的处理时间很快

时间:2014-07-25 17:10:55

标签: c# windows-phone-8 scheduled-tasks reverse-geocoding background-agent

我的Windows Phone 8天气应用程序的后台代理有问题。

每当运行后台代理时,当满足某些条件(与我遇到的问题无关)时,会发出新的http天气请求。当这些条件未得到满足时,将使用缓存的天气数据。

此外,如果您已将实时图块的位置设置为“当前位置”,则后台代理将使用反向地理编码来确定您当前所在位置的区域名称。无论是使用新数据还是缓存数据,即每次运行我的应用程序的后台代理时,都会执行此操作。

我遇到的问题是,每当使用缓存数据时,实时磁贴都不会更新。但它似乎不会导致异常发生,因为应用程序的后台代理永远不会被阻止,即使实时磁贴无法更新的次数超过两次。

这是后台代理的视图模型的“公共异步任务getWeatherForTileLocation()”方法的相关摘录,该方法是从预定代理调用的:

预定座席摘录:

protected async override void OnInvoke(ScheduledTask task)
{
    LiveTileViewModel viewModel = new LiveTileViewModel();
    await viewModel.getWeatherForTileLocation();

    // Etc.
}

getWeatherForTileLocation()摘录:

// If the default location is 'Current Location', then update its coordinates.
if ((int)IsolatedStorageSettings.ApplicationSettings["LocationDefaultId"] == 1)
{
    try
    {
        // Get new coordinates for current location.
        await this.setCoordinates();;
    }
    catch (Exception e)
    {

    }
}

// Depending on the time now, since last update (and many other factors),
// must decide whether to use cached data or fresh data
if (this.useCachedData(timeNow, timeLastUpdated))
{
    this.ExtractCachedData(); // This method works absolutely fine, trust me. But the live tile never updates when it's run outside debugging.
                              // Not because of what it does, but because of how fast it executes.
}
else
{
    // a httpClient.GetAsync() call is made here that also works fine.
}

setCoordinates方法,以及从中调用的反向地理编码相关方法:

public async Task<string> setCoordinates()
{
    // Need to initialise the tracking mechanism. 
    Geolocator geolocator = new Geolocator();

    // Location services are off.
    // Get out - don't do anything.
    if (geolocator.LocationStatus == PositionStatus.Disabled)
    {
        return "gps off";
    }
    // Location services are on.
    // Proceed with obtaining longitude + latitude.
    else
    {
        // Setup the desired accuracy in meters for data returned from the location service.
        geolocator.DesiredAccuracyInMeters = 50;

        try
        {
            // Taken from: http://bernhardelbl.wordpress.com/2013/11/26/geolocator-getgeopositionasync-with-correct-timeout/
            // Because sometimes GetGeopositionAsync does not return. So you need to add a timeout procedure by your self.

            // get the async task
            var asyncResult = geolocator.GetGeopositionAsync();
            var task = asyncResult.AsTask();

            // add a race condition - task vs timeout task
            var readyTask = await Task.WhenAny(task, Task.Delay(10000));
            if (readyTask != task) // timeout wins
            {
                return "error";
            }

            // position found within timeout
            Geoposition geoposition = await task;

            // Retrieve latitude and longitude.
            this._currentLocationLatitude = Convert.ToDouble(geoposition.Coordinate.Latitude.ToString("0.0000000000000"));
            this._currentLocationLongitude = Convert.ToDouble(geoposition.Coordinate.Longitude.ToString("0.0000000000000"));

            // Reverse geocoding to get your current location's name.
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                this.setCurrentLocationName();
            });

            return "success";
        }
        // If there's an error, may be because the ID_CAP_LOCATION in the app manifest wasn't include. 
        // Alternatively, may be because the user hasn't turned on the Location Services.
        catch (Exception ex)
        {
            if ((uint)ex.HResult == 0x80004004)
            {
                return "gps off";
            }
            else
            {
                // Something else happened during the acquisition of the location.
                // Return generic error message.
                return "error";
            }
        }
    }
}

/**
 * Gets the name of the current location through reverse geocoding.
 **/
public void setCurrentLocationName()
{
    // Must perform reverse geocoding i.e. get location from latitude/longitude.
    ReverseGeocodeQuery query = new ReverseGeocodeQuery()
    {
        GeoCoordinate = new GeoCoordinate(this._currentLocationLatitude, this._currentLocationLongitude)
    };
    query.QueryCompleted += query_QueryCompleted;
    query.QueryAsync();
}

/**
 * Event called when the reverse geocode call returns a location result.
 **/
void query_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
    foreach (var item in e.Result)
    {
        if (!item.Information.Address.District.Equals(""))
            this._currentLocation = item.Information.Address.District;
        else
            this._currentLocation = item.Information.Address.City;

        try
        {
            IsolatedStorageSettings.ApplicationSettings["LiveTileLocation"] = this._currentLocation;
            IsolatedStorageSettings.ApplicationSettings.Save();
            break;
        }
        catch (Exception ee)
        {
            //Console.WriteLine(ee);
        }
    }
}

我已多次调试代码,并且在我拥有时没有发现任何问题。调用时http请求是好的,缓存数据提取是好的,反向地理编码总是返回一个位置(最终)。

但我确实注意到,当我使用缓存数据时,在计划任务创建更新的实时磁贴之后但在计划任务完成之前检索当前位置的名称。

也就是说,在运行预定代理程序中的此代码后,将检索该位置的名称:

extendedData.WideVisualElement = new LiveTileWideFront_Alternative()
{
    Icon = viewModel.Location.Hourly.Data[0].Icon,
    Temperature = viewModel.Location.Hourly.Data[0].Temperature,
    Time = viewModel.Location.Hourly.Data[0].TimeFull.ToUpper(),
    Summary = viewModel.Location.Hourly.Data[0].Summary + ". Feels like " + viewModel.Location.Hourly.Data[0].ApparentTemperature + ".",
    Location = IsolatedStorageSettings.ApplicationSettings["LiveTileLocation"].ToString().ToUpper(),
    PrecipProbability = viewModel.Location.Hourly.Data[0].PrecipProbabilityInt
};

但之前:

foreach (ShellTile tile in ShellTile.ActiveTiles)
{
    LiveTileHelper.UpdateTile(tile, extendedData);
    break;
}

NotifyComplete();

显然由于内存限制,我无法在此时创建更新的可视元素。

为了进行比较,当我没有使用缓存数据时,反向地理编码查询总是设法在http请求代码完成之前返回一个位置。

因为视图模型的getWeatherForTileLocation()方法在调度代理中使用“await”,所以我决定确保在检索到当前位置的名称之前该方法不返回任何内容。我在方法的页脚中添加了一个简单的while循环,它只会在_currentLocation字段收到一个值后终止,即反向地理编码已完成:

// Keep looping until the reverse geocoding has given your current location a name.
while( this._currentLocation == null )
{

}

// You can exit the method now, as you can create an updated live tile with your current location's name now.
return true;

当我调试时,我认为这个循环运行了大约300万次迭代(无论如何都是一个非常大的数字)。但是这个黑客(我不知道怎么形容它)似乎在我调试时起作用。也就是说,当我的构建目标是我的Lumia 1020时,以及当我创建一个新鲜的实时图块时,它会调用:

ScheduledActionService.Add(periodicTask);
ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(1)); 

确保我不必等待第一个计划任务。当我调试第一个计划任务时,一切正常:1)反向地理编码请求,2)正确提取缓存数据,3)hacky while循环保持迭代,4)当反向地理编码返回位置名称时停止,5 )tile成功更新。

但后续使用缓存数据的后台代理调用似乎不会更新磁贴。只有在使用非缓存数据时,才会更新实时磁贴。我应该提醒你,反向地理编码查询总是设法在http请求代码完成之前返回一个位置,即hacky循环只迭代一次。

为了确保在使用缓存数据时正确更新实时磁贴,我需要做些什么(读取:在进行反向地理编码查询后处理数据时,比http请求快得多) )?另外,是否有更优雅的方法来阻止getWeatherForTileLocation()退出而不是我的while循环?我确定有!

对于长篇文章感到抱歉,但希望尽可能彻底!

在过去的72小时里,这一直给我一个不眠之夜(字面意思),所以非常感谢你的帮助和指导。

非常感谢。

巴蒂

3 个答案:

答案 0 :(得分:1)

你在提供大量细节方面做得很好,但它非常不连贯,所以很难理解。我认为问题的根源如下:

// Reverse geocoding to get your current location's name.
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
    this.setCurrentLocationName();
});

您正在尝试获取位置名称,但是当setCurrentLocationName方法执行时,您的setCoordinates方法已经完成。

现在因为你需要在UI线程中进行任何平铺更新,我建议只是从开始调度:

protected async override void OnInvoke(ScheduledTask task)
{
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        LiveTileViewModel viewModel = new LiveTileViewModel();
        await viewModel.getWeatherForTileLocation();
    }
}

这将消除将来进行任何其他调度的需要。

还有两件事:

通常,天气数据包括您获取数据的位置的名称。如果是这种情况,只需使用该数据而不是反向地理编码。这样可以节省一些内存并节省时间。

如果您确实需要获取该位置,我可能会建议您提取一个&#34; LocationService&#34;可以为您获取数据。在这个类中,您可以使用TaskCompltionSource来等待事件,而不是让代码遵循许多不同的路径。

public class LocationService
{
    public static Task<Location> ReverseGeocode(double lat, double lon)
    {
        TaskCompletionSource<Location> completionSource = new TaskCompletionSource<Location>();
        var geocodeQuery = new ReverseGeocodeQuery();
        geocodeQuery.GeoCoordinate = new GeoCoordinate(lat, lon);

        EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> query = null;
        query = (sender, args) =>
            {
                geocodeQuery.QueryCompleted -= query;
                MapLocation mapLocation = args.Result.FirstOrDefault();
                var location = Location.FromMapLocation(mapLocation);
                completionSource.SetResult(location);
            };
        geocodeQuery.QueryCompleted += query;
        geocodeQuery.QueryAsync();
    }
    return completionSource.Task;
}

使用TaskCometionSource可以等待方法,而不是使用事件。

var location = await locationService.ReverseGeocode(lat, lon);

此示例使用我创建的另一个Location类,只需保存City和State之类的内容。

后台代理的关键是确保代码始终同步&#34;同步&#34;。这并不意味着代码不能异步,但确实意味着代码需要一个接一个地调用。因此,如果您拥有包含事件的内容,则可以在事件发生后继续所有其他代码。

希望有所帮助!

答案 1 :(得分:1)

我没有看到你的延期电话。当您使用异步时,您必须告诉任务您将完成推迟到以后。不能记住我头脑中的方法,但它可以在你的后台任务的基类上,也可以在你得到的参数上。 它可能与缓存数据一起使用的原因是它可能实际上不是异步操作。

答案 2 :(得分:0)

我认为现在已经整理好了!非常感谢Shawn的帮助。现在等待setLocationName()方法调用,现在看起来像这样:

    public Task<string> setLocationName()
    {
        var reverseGeocode = new ReverseGeocodeQuery();
        reverseGeocode.GeoCoordinate = new System.Device.Location.GeoCoordinate(this._currentLocationLatitude, this._currentLocationLongitude );

        var tcs = new TaskCompletionSource<string>();
        EventHandler<QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>>> handler = null;
        handler = (sender, args) =>
        {

                MapLocation mapLocation = args.Result.FirstOrDefault();
                string l;
                if (!mapLocation.Information.Address.District.Equals(""))
                    l = mapLocation.Information.Address.District;
                else
                    l = mapLocation.Information.Address.City;

                try
                {
                    System.DateTime t = System.DateTime.UtcNow.AddHours(1.0);
                    if (t.Minute < 10)
                        IsolatedStorageSettings.ApplicationSettings["LiveTileLocation"] = l + " " + t.Hour + ":0" + t.Minute;
                    else
                        IsolatedStorageSettings.ApplicationSettings["LiveTileLocation"] = l + " " + t.Hour + ":" + t.Minute;
                    IsolatedStorageSettings.ApplicationSettings.Save();

                    this._currentLocationName = IsolatedStorageSettings.ApplicationSettings["LiveTileLocation"].ToString();
                }
                catch (Exception ee)
                {
                    //Console.WriteLine(ee);
                }

                reverseGeocode.QueryCompleted -= handler;
                tcs.SetResult(l);
        };

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