简体   繁体   English

我的后台代理中的反向地理编码导致我的活动磁贴在代理调用的其余代码的处理时间很快时无法更新

[英]Reverse geocoding in my background agent causes my live tile not to update when processing times for rest of code called by agent is fast

I'm having a problem with my Windows Phone 8 weather app's background agent. 我的Windows Phone 8天气应用程序的后台代理出现问题。

Whenever the background agent is run, a new http weather request is made when certain conditions (that are not relevant to the problem I'm having) are met. 每当后台代理运行时,当满足某些条件(与我遇到的问题无关)时,都会发出新的http天气请求。 When these conditions are unmet, cached weather data is used instead. 如果未满足这些条件,则将使用缓存的天气数据。

Furthermore, if you have set your live tile's location to your "Current Location", the background agent will use reverse geocoding to determine the name of the area for the location you're currently at. 此外,如果您将活动磁贴的位置设置为“当前位置”,则后台代理将使用反向地理编码来确定当前所在位置的区域名称。 This is done whether new or cached data is used ie every time my app's background agent is run. 无论使用新数据还是缓存数据,即每次运行我的应用程序的后台代理时,都可以完成此操作。

The problem I'm having is that whenever cached data is used, the live tile is not updating. 我遇到的问题是,每当使用缓存的数据时,实时磁贴都不会更新。 But it doesn't appear to cause an exception to occur because the app's background agent never gets blocked, even if there's been more than two times where the live tile fails to update. 但这似乎不会引起异常发生,因为该应用程序的后台代理从未被阻止,即使活动磁贴更新失败的次数超过两次。

This is the relevant excerpt from the background agent's view model's "public async Task getWeatherForTileLocation()" method that's called from the scheduled agent: 这是从后台代理的视图模型的“公共异步任务getWeatherForTileLocation()”方法的相关摘录,该方法是从计划的代理调用的:

Scheduled agent excerpt: 预定代理商摘要:

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

    // Etc.
}

getWeatherForTileLocation() excerpt: 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.
}

The setCoordinates method, as well the reverse geocoding related methods that are called from it: 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);
        }
    }
}

I've debugged the code many times, and have found no problems when I have. 我已经调试了很多次代码,但发现没有问题。 The http request when called is good, cached data extraction is good, reverse geocoding does always return a location (eventually). 调用http请求时是正确的,缓存数据提取是正确的,反向地理编码确实总是返回一个位置(最终)。

But I did notice that when I'm using cached data, the name of the current location is retrieved AFTER the scheduled task has created the updated live tile but before the scheduled task has finished. 但是我确实注意到,当我使用缓存的数据时,在计划的任务创建了更新的实时图块之后但在计划的任务完成之前就检索了当前位置的名称。

That is, the name of the location is retrieved after this code in the scheduled agent is run: 也就是说,在调度的代理中运行以下代码后,将检索位置的名称:

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

But before: 但之前:

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

NotifyComplete();

Obviously due to memory constraints I can't create an updated visual element at this point. 显然由于内存限制,我目前无法创建更新的视觉元素。

For comparison, when I'm not using cached data, the reverse geocoding query always manages to return a location before the http request code has finished. 为了进行比较,当我不使用缓存的数据时,反向地理编码查询始终设法在http请求代码完成之前返回一个位置。

So as the view model's getWeatherForTileLocation() method is using "await" in the scheduled agent, I decided to make sure that the method doesn't return anything until the current location's name has been retrieved. 因此,由于视图模型的getWeatherForTileLocation()方法在计划的代理中使用“等待”,因此我决定确保在检索到当前位置的名称之前,该方法不返回任何内容。 I added a simple while loop to the method's footer that would only terminate after the _currentLocation field has received a value ie the reverse geocoding has completed: 我在方法的页脚中添加了一个简单的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;

When I debugged, I think this loop ran around 3 million iterations (a very big number anyway). 当我调试时,我认为这个循环运行了大约300万次迭代(无论如何,这是一个很大的迭代)。 But this hack (I don't know how else to describe it) seemed to work when I'm debugging. 但是当我调试时,这种hack(我不知道该如何形容)似乎有效。 That is, when the target of my build was my Lumia 1020, and when I created a live tile fresh from it, which calls: 也就是说,当我构建的目标是我的Lumia 1020时,并且当我从中创建一个新鲜的活动磁贴时,它调用:

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

To ensure I don't have to wait for the first scheduled task. 为了确保我不必等待第一个计划的任务。 When I debugged this first scheduled task, everything works fine: 1) a reverse geocoding request is made, 2) cached data extracted correctly, 3) hacky while loop keeps iterating, 4) stops when the reverse geocoding has returned a location name, 5) tile gets updated successfully. 当我调试此第一个计划任务时,一切正常:1)进行了反向地理编码请求,2)正确提取了缓存的数据,3)hacky而循环保持迭代,4)当反向地理编码返回了位置名称时停止,5 )瓷砖更新成功。

But subsequent background agent calls that do use cached data don't appear to update the tile. 但是,随后的确实使用缓存数据的后台代理调用似乎并未更新磁贴。 It's only when non-cached data is used that the live tile updates. 仅当使用非缓存数据时,实时磁贴才会更新。 I should remind you at this point the reverse geocoding query always manages to return a location before the http request code has finished ie the hacky loop iterates only once. 我应该在此提醒您,反向地理编码查询始终设法在http请求代码完成之前返回一个位置,即hacky循环仅迭代一次。

Any ideas on what I need to do in order to ensure that the live tile updates correctly when cached data is used (read: when the processing of data, after the reverse geocoding query is made, is much faster than a http request)? 为了确保在使用缓存数据时确保活动图块正确更新,我需要做什么(请阅读:在进行反向地理编码查询后进行数据处理时,它比http请求要快得多)吗? Also, is there a more elegant way to stop the getWeatherForTileLocation() from exiting than my while loop? 另外,有没有比while循环更优雅的方法来阻止getWeatherForTileLocation()退出? I'm sure there is! 我敢肯定有!

Sorry for the long post but wanted to be as thorough as possible! 抱歉,很长的帖子,但希望您能做到透彻!

This has been giving me sleepless nights (literally) for the last 72 hours, so your help and guidance would be most appreciated. 在过去的72个小时里,这给了我不眠之夜(从字面上看),因此,感谢您的帮助和指导。

Many thanks. 非常感谢。

Bardi 巴迪

You have done a great job of providing lots of detail, but it is very disconnected so it is a litle hard to follow. 您已经做了很多工作,提供了很多细节,但是它是非常独立的,因此很难遵循。 I think the root of your problem is the following: 我认为问题的根源如下:

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

You are attempting to get the location name, but your setCoordinates method will have already completed by the time the setCurrentLocationName method gets around to executing. 您正在尝试获取位置名称,但是在setCurrentLocationName方法开始执行时,setCoordinates方法将已经完成。

Now because you need to be in a UI thread to do any tile updating anyways, I would suggest just dispatching from the begining: 现在,因为您需要进入UI线程才能进行任何切片更新,所以我建议从头开始进行调度:

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

This would remove the need to do any other dispatching in the future. 这将消除将来进行任何其他调度的需要。

Two more things: 还有两件事:

Generally weather data includes the name of the location you are getting data for. 通常,天气数据包括您要获取数据的位置的名称。 If this is the case, just use that data rather than doing the reverse geocode. 如果是这种情况,请仅使用该数据,而不要进行反向地址解析。 This will save you some memory and save time. 这样可以节省一些内存并节省时间。

If you do need to get the location, I might suggest pulling out a "LocationService" that can get data for you. 如果确实需要获取位置,则建议您提取一个“ LocationService”以获取数据。 In this class you can use a TaskCompltionSource to await the event rather than having code follow many different paths. 在此类中,您可以使用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;
}

Using a TaskCometionSource allows you to await the method rather than using the event. 使用TaskCometionSource可以让您等待方法而不是使用事件。

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

This example uses another Location class that I created do just hold things like City and State. 这个例子使用了我创建的另一个Location类,只包含City和State之类的东西。

The key thing with background agents is to ensure that code always flows "synchronously". 后台代理的关键是确保代码始终“同步”流动。 This doesn't mean code cannot be asynchronous, but does mean that code needs to be called one after the other. 这并不意味着代码不能异步,而是意味着代码需要一个接一个地调用。 So if you have something that has events, you could continue all other code after the event. 因此,如果您有发生事件的事物,则可以在事件之后继续执行所有其他代码。

Hope that helps! 希望有帮助!

I don't see your deferral call. 我看不到您的延期电话。 When you use async you have to tell the task that you're deferring completion till later. 使用异步时,您必须告诉任务您将完成推迟到以后。 Can't remember the method off the top of my head but it's either on the base class of your background task or on the parameter you get. 我不记得这个方法了,但它是在后台任务的基类上,还是在您获得的参数上。 The reason it probably works with cache data is that it probably isn't actually an async operation. 它可能与缓存数据一起使用的原因是,它实际上可能不是异步操作。

I think this is sorted now! 我认为这是现在排序! Thanks so much Shawn for the help. 非常感谢Shawn的帮助。 The setLocationName() method call is now awaited, and it looks like this now: 现在正在等待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;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM