One of the most important features added in Windows Phone 7.5 is the multithreaded environment. This missing feature was very criticized in the previous version, but the main reason of its lack was the high battery consumption that is usual in multithread phones. In this release the team has agreed to add the multithread capability but it worked hard to reach a good balance between multithreading and battery life. This is the main reason for the introduction of Background Agents that are a way to manage the battery drain in junction with parallel work made by running applications.
Download the source code
Understanding Agents
Starting from the release of OS7.5, the developer can create background agents. As the name suggests, an agent works in background, hosted in a separated thread and is able to make some work. Important to say is that an agent must be initiated by an application. The application has to register the agent on the ScheduledActionService then it can continue its work or exit and the agent will be called by the Scheduler seamless. This imply that agents are not daemons; there is not any way to register and run the agent like a gui-less application that perform its work continuously. This just because agents are designed with battery life in mind so they only can be scheduled to run in two ways:
-
PeriodicTask – These agents are called every 30 minutes (the timeout is fixed and cannot be changed). They can run for a very short timeframe and perform lightweight tasks
-
ResourceIntensiveTask – These agents are triggered by a set of requirements like processor activity, network, power and so on. They can work for a relatively long period.
The limits imposed to the developer are really heavy; You can only register two background agents per application, and they have to be of different type. This means your application can only have a single PeriodicTask and a single ResourceIntensiveTask.
To create the background agent you can start in the same way we did when I’ve explained the Audio Playback Agent. In Visual Studio you can open the Add New Project dialog and choose the Windows Phone Scheduled Task Agent. This option will create a new project and a ScheduledAgent class that is already configured and ready to be filled with your implementation. Then, when you connect this project with the main application, the WMAppManifest.xml file is modified with the reference to the class. Differently from an Audio Playback Agent this does not suffice to make your agent up and running. You have obviously to implement you agent logic but you have also to register the agent with the ScheduledActionService:
1: private void RegisterTasks()
2: {
3: string taskName = ScheduledAgent.Name;
4:
5: PeriodicTask existing = ScheduledActionService.Find(taskName) as PeriodicTask;
6:
7: if (existing != null)
8: {
9: ScheduledActionService.Remove(taskName);
10: }
11:
12: PeriodicTask task = new PeriodicTask(taskName)
13: {
14: Description = "Download local images from panoramio"
15: };
16:
17: ScheduledActionService.Add(task);
18: }
This code may be added to the App.xaml.cs and its purpose is to check if an instance of the agent already exists before adding it to the ScheduledAgentService. The service accept an instance of PeriodicAgent or ResourceIntensiveTask but the sole reference to your implementation is in the taskName that has to be the name specified in the WMAppManifest.xaml. The PeriodicTask, I used in this example, has some other properties in addition to Description but none of them let you specify the timing of the schedule. Unfortunately it is fixed to 30 minutes.
Create an agent
Implementing the agent is very straightforward. The class that implements the agent is inherited by ScheduledTaskAgent and it exposes an OnInvoke method. This method is called every time the scheduler needs to execute your code. The method provide a single parameter that is useful to know some information about the task that is running. As an example you can know the LastScheduledTime or the LastExitReason that contains the following values:
1: public enum AgentExitReason
2: {
3: None,
4: Completed,
5: Aborted,
6: MemoryQuotaExceeded,
7: ExecutionTimeExceeded,
8: UnhandledException,
9: Terminated,
10: Other,
11: }
Another important reason to use the provided parameter is to understand if your task is running as a PeriodicTask or ResourceIntensiveTask. This is useful when you register the sample agent for both the types. You can understand when you are in one case or in the other examining the underlying type:
1: if (task is PeriodicTask)
2: {
3: // do the periodic job
4: }
5: else
6: {
7: // do the resource intensive job
8: }
You have to be careful when you implement the agent because the type you choose gives you some limits. Particularly the PeriodicTask is designed to be lightweight and the code you put inside the OnInvoke method must have a very light thumbprint in terms of elaboration time and used resources. As the AgentExitReason testifies, the runtime can kill your work in case of MemoryQuotaExceeded and in case of ExecutionTimeExceeded. A periodic task can run only for about 25 seconds and can allocate a maximum of 5 MB of memory. A resource intensive task instead can run for about 10 minutes before the runtime kill it.
Given these limits and the timeout of 30 minutes between one execution an the other, you can easily understand that debugging an agent may become a nightmare. If you run the agent in its normal context you have to wait for 30 minutes before the breakpoints are hit. On the other side if you test the agent outside of the context you can easily overwhelm the resource limits and then discover that your agent won’t run when executed by the scheduler. To simplify the debug tasks it exists a LaunchForTest method into the ScheduledActionService. This method let you specify a timeout, useful to exit the application manually, then it runs the agent once without waiting for the expiration of the regular timeout. You can only debug the agent once per session but this method is important to speed-up your work.
ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(5));
A funny example
To demonstrate the use of agents I’ve created an interesting and beautiful example. If you used google maps at least once, you know about Panoramio. It is a funny service that is able to show geo referenced photos over the google map. For this example I’ve managed to acquire the GPS position of the device when the agent run, download the available images and populate its tile with one randomized on the first twenty. The effect id pretty beautiful, expecially if you are moving during a travel because once every 30 minutes the tile is updated with a photo of a place located near you.
In the Invoke method I use the GeoCoordinateWatcher to read the current position. The problem here is about the execution model of the GeoCoordinateWatcher. It is made to notify when the position has changed raising an event. Here is the code I’ve prepared:
1: protected override void OnInvoke(ScheduledTask task)
2: {
3: GeoCoordinateWatcher watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High)
4: {
5: MovementThreshold = 10
6: };
7:
8: watcher.PositionChanged += (s, e) =>
9: {
10: watcher.Stop();
11:
12: GeoCoordinate position = e.Position.Location;
13:
14: if (position.IsUnknown)
15: {
16: Random rnd = new Random(DateTime.Now.Millisecond);
17: position = this.PointOfInterests[rnd.Next(0, this.PointOfInterests.Length - 1)];
18: }
19:
20: this.UpdateImage(position);
21: };
22:
23: watcher.Start(false);
24: }
In this snippet I initialize the GeoCoordinateWatcher, then I attach the PositionChanged event. This event immediately notifies the new position and it is here I retrieve the current position. In the Positionchanged event I check the retrieved position; if it is Unknown I use a special array, filled with a number of random positions, to provide a random location to display.
In the UpdateImage I use the Panoramio’s API to retrieve the available images using the Json format. In the GetRandomLocalImage I raffle an image from the set I found so in case of repeated update the photo will change every time:
1: private void UpdateImage(GeoCoordinate position)
2: {
3: if (position != null)
4: {
5: PanoramioService.GetRandomLocalImage(
6: position.Latitude,
7: position.Longitude,
8: s =>
9: {
10: this.LoadImage(s);
11: this.NotifyComplete();
12: },
13: ex => this.NotifyComplete());
14: }
15: }
Important to say, no matter whether I found or not an image, I call the NotifyComplete method. This method let the runtime know that the work is done. Finally in the LoadImage method I use the new API dedicated to the tiles to change the background image and the title according with the found image:
1: private void LoadImage(PanoramioImageResult image)
2: {
3: ShellTile tile =
4: ShellTile.ActiveTiles.FirstOrDefault();
5:
6: if (tile != null)
7: {
8: tile.Update(
9: new StandardTileData
10: {
11: Title = image.Title,
12: BackgroundImage = image.File,
13: Count = null
14: });
15: }
16: }
A beautiful opportunity
There is no doubts that Background Agents are something missing in OS7.0. There’s a lot of cases when you can enrich your applications with some background notifiers and tasks. I hope in the next releases it will be removed some of the annoying limits, first of all the fixed 30 minutes schedule, that is really hard to understand, also with the purpose of sparing battery. I think a little configurability would be better.