If you are a desktop or web developer, you are
used to being able to decide when and where your code runs. On the
phone you do not have that luxury, so you have to let the operating
system dictate exactly when you run your application or some background
operations. But how does the operating system handle background
operations?
Normally when you build a
typical phone application, you submit a .xap file with all the code your
application requires to launch the application’s user interface. Your
application can be launched, but when it is not in the foreground, none
of your code will execute.
Background agents enable you to supply some code
that is executed periodically by the operating system. This code does
not have any user interface but shares information with the main
application. The information it can share includes isolated storage and
application storage (for example, where the .xap file contents are
located), as shown in Figure 1.
FIGURE 1 Relationship between application and scheduled task
Although the main application is an assembly
that contains the startup code, a background agent works similarly. The
background agent consists of an additional assembly that is included in
the main application’s .xap file and contains the code to execute in the
background. The main goal of the operating system is to protect the
phone from you, the developer. All agents have some specific
limitations, as shown in Table 1.
TABLE 1 Scheduled Task Limitations
Now that you understand the basic reasons and limitations of using background agents, let’s look at each agent type.
1. Periodic Agent
The periodic background agent
is a background agent that is meant to execute some code every 30
minutes. To optimize the battery life of the phone, this can wander as
much as 10 minutes earlier or later to align with operating system
processes. These processes can run for only a maximum of 25 seconds
(along with the limitations mentioned in Table 1). To get started, you will need a Schedule Task Agent project in your solution. To add a Scheduled Task Agent project, right-click the solution in the Solution Explorer and select Add | New Project.
After you are in the Add New Project dialog,
select the Windows Phone Scheduled Task Agent project type under the
Windows Phone section of your language, as shown in Figure 2.
FIGURE 2 Selecting the Windows Phone Scheduled Task Agent
This creates a new project in your solution that contains a single class file:
using Microsoft.Phone.Scheduler;
public class ScheduledAgent : ScheduledTaskAgent
{
static ScheduledAgent()
{
// Subscribe to the managed exception handler
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
Application.Current.UnhandledException += UnhandledException;
});
}
static void UnhandledException(object sender,
ApplicationUnhandledExceptionEventArgs e)
{
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
protected override void OnInvoke(ScheduledTask task)
{
//TODO: Add code to perform your task in background
NotifyComplete();
}
}
The ScheduledAgent
class created overrides a single method (OnInvoke
)
that the operating system calls when the background agent is executed.
You should do your work in this method and have the call to NotifyComplete
be the last line of code in this method. The NotifyComplete
method tells the operating system that your operation is complete. For
example, to save a file with the current time in isolated storage (so
your UI app can use it), you could do this:
protected async override void OnInvoke(ScheduledTask task)
{
var folder = ApplicationData.Current.LocalFolder;
using (var stream =
await folder.OpenStreamForWriteAsync("time.txt",
CreationCollisionOption.ReplaceExisting))
{
var writer = new StreamWriter(file);
writer.WriteLine(DateTime.Now);
}
NotifyComplete();
}
Because this code writes directly to isolated
storage for the application, that means the main application can read
the file you created here. As shown here, you can call the NotifyComplete
method when you have completed your operation. You also might want to
know whether the operation fails. You can accomplish this by calling the
Abort
method:
try
{
var folder = ApplicationData.Current.LocalFolder;
using (var stream =
await folder.OpenStreamForWriteAsync("time.txt",
CreationCollisionOption.ReplaceExisting))
{
var writer = new StreamWriter(stream);
writer.WriteLine(DateTime.Now);
}
NotifyComplete();
}
catch
{
Abort();
}
You should call either NotifyComplete
or Abort
in your agent, to let the runtime (and potentially your application) know whether the task was successfully completed.
When you added the new scheduled agent, the project also reached into the main application and added a new section to the WMAppManifest.xml
file:
<Deployment ...>
<App ...>
...
<Tasks>
<DefaultTask Name="_default"
NavigationPage="MainPage.xaml" />
<ExtendedTask Name="BackgroundTask">
<BackgroundServiceAgent Specifier="ScheduledTaskAgent"
Name="BackgroundAgent"
Source="BackgroundAgent"
Type="BackgroundAgent.ScheduledAgent"
/>
</ExtendedTask>
</Tasks>
...
</App>
</Deployment>
Inside the Tasks
element, the project item added a section called ExtendedTask
, which is responsible for indicating the project and code for any background tasks. The ExtendedTask
element is where all agents are registered, including periodic, resource-intensive, and audio agents. Although the ExtendedTask
is named, the name is not significant. Inside the ExtendedTask
element is a set of elements that reference the different background
agent or agents in your application. Each attribute in the BackgroundServiceAgent
element has a specific meaning:
• Name: This is the name of the element, not referenced in code.
• Specifier: This is the type of agent. The types of specifiers are as follows:
– ScheduledTaskAgent: This is a periodic or resource-intensive task.
– AudioPlayerAgent: This task plays specific songs from a list of audio files.
– AudioStreamingAgent: This task streams audio directly to the phone.
• Source: This is the assembly name that contains the background agent.
• Type: This is the type of the class that represents the background agent.
This part of the WMAppManifest.xml
file is what links your application to the assembly that contains your
background task. This means your background agent must be in a separate
assembly (as the separate project would indicate). You still have to do a
little more work to make your agent actually run in the background.
Before your application can register the
background task, you need to make a reference to the new background
project. This just requires you to select Add Service Reference and pick
the assembly in the Solution tab, as shown in Figure 3.
FIGURE 3 Adding a reference to the Scheduled Task Agent project
After your main project has
a reference to the background task, you can register it to be executed
periodically. To do this, you need to create a new instance of your task
using the PeriodicTask
class:
// A unique name for your task. It is used to
// locate it in from the service.
var taskName = "MyTask";
// Create the Task
PeriodicTask task = new PeriodicTask(taskName);
// Description is required
task.Description = "This saves some data to Isolated Storage";
// Add it to the service to execute
ScheduledActionService.Add(task);
The unique name here is used to locate the
service if you need to stop or renew the service, but it is not related
to the task name in the WMAppManifest.xml
file. After your task is created, you must set the Description
property as well (at a minimum). After your PeriodicTask
object is constructed, you can add it to the phone by using the ScheduledActionService
’s Add
method as shown. This will cause your background task to be periodically executed (every 30 minutes).
You should set the Description
property of the PeriodicTask
class to something significant. The description is a string that is
visible to the end user and that is shown in the background task
management UI (see Figure 4).
FIGURE 4 The PeriodicTask’s description in the management user interface
Each periodic task will
execute for up to two weeks before it has to be reregistered. The only
exception to this is if you are updating your live tile. When you update
the live tile (either through the app or through a background agent),
this will extend the length of the registration another two weeks. When
you launch your app, you should remove and re-create it on every
execution of your application:
// A unique name for your task. It is used to
// locate it in from the service.
var taskName = "MyTask";
// If the task exists
var oldTask = ScheduledActionService.Find(taskName);
if (oldTask != null)
{
ScheduledActionService.Remove(taskName);
}
// Create the Task
PeriodicTask task = new PeriodicTask(taskName);
// Description is required
task.Description = "This saves some data to Isolated Storage";
// Add it to the service to execute
ScheduledActionService.Add(task);
Now that you have your agent registered, you
will need to be able to debug it. The problem on the face of it is that
you might not want to wait the 30 minutes for your agent to execute. The
ScheduledActionService
has a way to run the agent immediately so that you can debug it more easily:
var taskName = "MyTask";
ScheduledActionService.LaunchForTest(taskName,
TimeSpan.FromMilliseconds(250));
The LaunchForTest
method takes the name of the task (which you specified earlier when you created the PeriodicTask
)
and a delay before the task is launched. Lastly, because the background
task (in this example) was able to write to isolated storage, you can
access that data in your main application anytime you want. The
background task and your application simply need to communicate by
storing information in these shared locations (local folder, the
Internet, or reading from the installation folder).
In addition, you might want to alert the user
about new information the background task detected (for example, a new
message is available). You can use the ShellToast
class to open a toast (or update Live Tiles):
protected override void OnInvoke(ScheduledTask task)
{
// If the Main App is Running, Toast will not show
ShellToast popupMessage = new ShellToast()
{
Title = "My First Agent",
Content = "Background Task Launched",
NavigationUri = new Uri("/Views/DeepLink.xaml", UriKind.Relative)
};
popupMessage.Show();
NotifyComplete();
}
By using the ShellToast
class, you
can alert the user that the background task detected something and give
her a chance to launch the application. If the main application is
currently running, the ShellToast
class will not show the pop-up and will be reserved to show when your application is not currently being executed.
Creating your own periodic tasks is an easy way
to do simple background processing, be able to alert the user to ongoing
events, and allow her to interact with your application. But sometimes
you will need a periodic task that consumes more resources. That is
where resource-intensive agents come in.