0

我的 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() 方法在调度代理中使用“等待”时,我决定确保该方法在检索到当前位置的名称之前不会返回任何内容。我在方法的页脚中添加了一个简单的 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 万次迭代(无论如何是一个非常大的数字)。但是这个 hack(我不知道如何描述它)在我调试时似乎有效。也就是说,当我的构建目标是我的 Lumia 1020 时,当我从它创建一个新鲜的动态磁贴时,它调用:

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

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

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

关于我需要做什么以确保在使用缓存数据时正确更新实时磁贴的任何想法(阅读:当数据处理时,在进行反向地理编码查询后,比 http 请求快得多)?另外,有没有比我的 while 循环更优雅的方法来阻止 getWeatherForTileLocation() 退出?我确定有!

抱歉,这篇文章很长,但希望尽可能彻底!

在过去的 72 小时里,这一直让我彻夜难眠(字面意思),因此非常感谢您的帮助和指导。

非常感谢。

巴迪

4

3 回答 3

1

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

于 2014-07-26T14:04:14.493 回答
1

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

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

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

现在,因为无论如何您都需要在 UI 线程中进行任何磁贴更新,所以我建议您从一开始就进行调度:

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

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

还有两件事:

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

如果您确实需要获取位置,我可能会建议您使用可以为您获取数据的“LocationService”。在此类中,您可以使用 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 之类的东西。

后台代理的关键是确保代码始终“同步”流动。这并不意味着代码不能是异步的,而是意味着代码需要一个接一个地调用。因此,如果您有一些有事件的东西,您可以在事件之后继续所有其他代码。

希望有帮助!

于 2014-07-26T13:07:51.287 回答
0

我想现在已经解决了!非常感谢肖恩的帮助。现在正在等待 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;
    }
于 2014-07-26T16:55:48.830 回答