In-Depth
What's New in Windows 8.1 Background Tasks
A look at the improvements and new features of the updated collection of background APIs.
- By Tony Champion
- 09/06/2013
The release of the Windows 8.1 Preview at the Build 2013 conference brought hundreds of new additions and enhancements to the Windows Runtime. Virtually every aspect of the API was affected in some way, and developers are sorting through the changes. The collection of background APIs was also impacted by the new release, and this article will walk through the improvements and new features.
Why Background APIs Exist
As a Windows Store app developer, it's important to understand the reason background APIs are there in the first place. Windows 8 was built from the ground up to be efficient and perform well. One of the ways this is accomplished is by suspending any Windows Store app that's not currently on the screen. When a user replaces an app on the screen, that app is maintained in memory but its threads are no longer being processed. This allows the user to maintain a large number of apps without degrading performance or battery life.
The downside to this lifecycle is that when your app isn't on the screen, it's no longer running. In a lot of scenarios this isn't a cause for concern. For example, having a game of solitaire suspended when it isn't on the screen isn't going to affect the users experience with that app. However, what if your app was playing music, transferring large files or running as an instant messenger client? In these situations, the user's experience would suffer if there weren't a way to address these use cases. This is where the background APIs come into play.
Exploring the New Additions
It's beyond the scope of this article to dig into the full details of all of the background APIs. However, there are many great explanations on the subject that can be found in articles and books, and on MSDN. These include my sessions at both TechEd North America and TechEd Europe, which are available on Channel 9. Here, I'll take a closer look at the changes in the three main types of background APIs: playing audio, transferring files and event-based tasks.
Playing Audio in the Background
Audio is played in most Windows Store apps through the MediaElement in XAML or the audio and video tags in HTML. In order for the audio to continue to play when the app's in the background, your app must do three things: add a declaration in the App Manifest, set the audio category to a supported background category and register to use the system transport controls. The only part that changes in Windows 8.1 is interaction with the system transport controls.
When an app's playing audio in the background, the user should still have some interaction with that audio. At a minimum he should be able to adjust the volume and play, pause or stop the audio completely. Windows handles this by providing a system transport control, as seen in Figure 1, which appears when the user presses any hardware media button on his device, such as the volume control. This presents the user with a consistent experience of managing the background audio.
In Windows 8, an app registered with a system transport control by attaching to events exposed by the Windows.Media.MediaTransport class. An app could register for events to stop, pause, and play the audio, as well as progress through tracks, adjust the volume, and provide some information about what was currently being played. The MediaTransport class will still work in Windows 8.1, but isn't guaranteed to run past that version. A new class, Windows.Media.SystemMediaTransportControls, has replaced MediaTransport going forward. While the SystemMediaTransportControls provides the same functionality as its predecessor, it also adds several new capabilities, which include the record, fast forward and rewind buttons.
Registering for these events is also slightly different. SystemMediaTransportControls isn't a static class that exposes events. Your app can access the control through the GetForCurrentView static method. Instead of having an event for each button in the controls, all button clicks are processed through a single ButtonPressed event.
Each feature has an associated Boolean property to enable it. For example, there's an IsPlayEnabled and IsPauseEnabled to enable the play and pause buttons. Only the IsPlayEnabled and IsPauseEnabled features must be enabled for the audio to play in the background.
An additional feature of the SystemMediaTransportControls is how the display information is populated. The information, including the thumbnail, can still be updated by properties exposed in the DisplayUpdater property, which is an instance of the SystemMediaTransportControlsDisplayUpdater class. However, you can now populate the display information directly from the metadata of a media file by using the CopyFromFileAsync method. Listing 1 is an example of implementing the media transport controls.
Listing 1. Implementation of the system media transport controls.
private void InitTransportControls() {
transportControls = SystemMediaTransportControls.GetForCurrentView();
transportControls.IsPlayEnabled = true;
transportControls.IsStopEnabled = true;
transportControls.IsPauseEnabled = true;
transportControls.ButtonPressed += transportControls_ButtonPressed;
}
private void transportControls_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
Play();
break;
case SystemMediaTransportControlsButton.Stop:
Stop();
break;
case SystemMediaTransportControlsButton.Pause:
Pause();
break;
}
}
private async void Play()
{
await coreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
media.Play();
transportControls.PlaybackStatus = MediaPlaybackStatus.Playing;
}
);
}
private async void Pause()
{
await coreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
media.Pause();
transportControls.PlaybackStatus = MediaPlaybackStatus.Paused;
}
);
}
private async void Stop()
{
await coreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
media.Stop();
transportControls.PlaybackStatus = MediaPlaybackStatus.Stopped;
}
);
}
Transferring Files in the Background
If your app needs to upload or download large files, the background-transfer APIs are the right tool for the job. The transfer APIs move the file transfer outside the scope of your app and will continue even if your app is suspended or terminated. They also come with a few more goodies. The transfer APIs are designed to be both power-efficient and network-resilient. They'll handle network outages and automatically continue transferring once the network resumes. You can also configure the process to not transfer the data (which is the default configuration) if the device is using a network that costs per usage.
Network Switching
Windows 8.1 didn't introduce any breaking changes to these APIs, and only adds some additional functionality. The transfer APIs have a new enhancement to their network resilience. If a file's being transferred on a network and a less-restricted network becomes available, the transfer API will automatically move the transfer to the new network if it's a resumable process. This means you'll get the most efficient transfer possible throughout the life of the process.
Completion Notifications
Four new properties were added to the BackgroundDownloader and BackgroundUploader classes. The first two properties, SuccessTileNotification and FailureTileNotification, allow you to update the app's live tile on the completion of the process. Similarly, there's a SuccessToastNotification and FailureToastNotification to notify the user through a toast notification.
This fills an important gap in the transfer process. While moving the transfer processes outside your app prevented them from being suspended or terminated with your app, there was no mechanism to inform the user of any sort of progress. The user would have to reopen the app and allow it to resync with the transfer processes to report on the progress. This didn't flow with the "always connected" feel of a Windows Store app. The new properties fill this gap through the tile and toast notifications. Listing 2 is an example of setting a toast notification to be sent when a download operation is completed.
Listing 2. Setting the success toast notification.
BackgroundDownloader downloader = new BackgroundDownloader();
// Create a ToastNotification when download is successful
XmlDocument successToastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
successToastXml.GetElementsByTagName("text").Item(0).InnerText =
"Download was successful.";
ToastNotification successToast = new ToastNotification(successToastXml);
downloader.SuccessToastNotification = successToast;
// Create a ToastNotification when download fails
XmlDocument failureToastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
failureToastXml.GetElementsByTagName("text").Item(0).InnerText =
"Download failed.";
ToastNotification failureToast = new ToastNotification(failureToastXml);
downloader.FailureToastNotification = failureToast;
// Create Download Operation
DownloadOperation op = downloader.CreateDownload(sourceUri, destinationLocation);
Transfer Groups
The background transfer APIs also added the concept of groups in transfers. By creating a group and associating transfers to that group, you have some control over how the files are transferred. Groups are defined with the BackgroundTransferGroup class.
Each BackgroundTransferGroup class must be assigned a unique name within the scope of the app. The BackgroundTransferGroup class has two properties: Name and TransferBehavior. The Name property is important because the BackgroundDownloader and BackgroundUploader classes use this name to assign operations to that group. The TransferBehavior property determines the overall transfer strategy. The strategy can be set to run all of the operations simultaneously, in parallel or one at a time, serialized. The following is an example of defining a background group and assigning a collection of background download operations to that group:
// Create a BackgroundDownloader and assign a TransferGroup
BackgroundDownloader downloader = new BackgroundDownloader();
downloader.TransferGroup = "MyDownloads";
// Create Download Operations in same group
DownloadOperation op1 = downloader.CreateDownload(sourceUri1, destinationLocation1);
DownloadOperation op2 = downloader.CreateDownload(sourceUri2, destinationLocation2);
DownloadOperation op3 = downloader.CreateDownload(sourceUri3, destinationLocation3);
Each DownloadOperation and UploadOperation is assigned a group priority through the Priority property. The BackgroundTransferPriority enum allows you to toggle the operation's priority between Default and High. If the transfer group's transfer behavior is set to Serialized, any operation set to a high priority will be transferred first. This allows you to provide some sort of ordering to your files. For example, if you were downloading a collection of episodes for a TV show, you could make sure the first episode downloads before the others so the user can start watching the show while the rest are being downloaded. The following code shows how to set the previous transfer group to be serialized and elevate the priority of the first download operation to download before the others:
// Get BackgroundTransferGroup from DownloadOperation
BackgroundTransferGroup group = op1.TransferGroup;
// Set behavior to Serialized
group.TransferBehavior = BackgroundTransferBehavior.Serialized;
// Change priority of first operation to download first
op1.Priority = BackgroundTransferPriority.High;
When retrieving the collection of running download or upload processing for the app, there are now two additional methods for getting just the processes associated with a group. First, the GetCurrentDownloadsAsync has a new override that accepts a string representing the name of the transfer group. Second, a new method called GetCurrentDownloadsForTransferGroupAsync retrieves a collection of download operations associated with a given instance of a BackgroundTransferGroup. As with everything in the transfer APIs, there's also a matching set of methods added for uploads.
Background Tasks
The majority of the new additions to the background APIs can be found in the background tasks. This includes new concepts, triggers and system event types. If you're new to background tasks, you can get a primer in my article, "Background Tasks in Windows Store Apps."
Background Work Costs
Background tasks have a new concept of a background work cost. The background work cost gives you a hint as to how much work the background thread currently has scheduled. This gives you an opportunity to choose to not run a background task if the background thread is currently under a heavy load.
The current work cost is returned in a BackgroundWorkCostValue enum that has three values: Low, Medium and High. The BackgroundWorkCost class has a single static property, CurrentBackgroundWorkCost, which returns the current BackgroundWorkCostValue of the system. This can be used to determine whether certain background tasks should be initialized or can also be checked at the beginning of an event being fired to see if the work should be completed at that time. The following example checks the device's work cost before registering a new MaintenanceTrigger:
// Get current background work cost
BackgroundWorkCostValue current = BackgroundWorkCost.CurrentBackgroundWorkCost;
// If cost isn't high, do some work
if (current != BackgroundWorkCostValue.High)
{
// Do some work
}
System Events
Two new system events have been added to the SystemTrigger. All of the original events have remained unchanged, and the new events have been added to the end of the SystemTriggerType enum to prevent breaking any prior usage of the enum in Windows 8.
The first event is the BacgkroundWorkCostChange event. To further the earlier discussion on the new background work cost concept, this event allows you to change your background API strategy based on what's currently going on with the system. For instance, you might want to change how often a MaintenanceTrigger task fires based on the current cost.
The other event is the NfcStateChange event. This is used to allow your app to respond to near field communication (NFC) events for your device even when the app isn't currently active.
Device Events
Windows 8.1 added the ability for Windows Store apps to communicate directly with attached peripheral devices, such as a Bluetooth device. In order to provide support for longer-running processes, two new background triggers were added: DeviceUseTrigger and DeviceServicingTrigger. What's unique about them is that they're the only triggers that can and must be manually fired in code. Therefore, both can only be fired when the app's currently active and running on the user's screen.
In addition, neither trigger is subject to the same CPU and network quotas to which the other triggers are held. This allows the processes to continue uninterrupted while communicating with the device. However, both of these triggers run on lower-priority threads to prevent them from causing adverse system performance.
DeviceUseTrigger
The purpose of the DeviceUseTrigger is to allow long-running syncing processes to run between the app and the device even while the app is suspended. Each time this trigger is fired, it can run up to 10 minutes; however, the device must remain paired or connected to the user's machine during the entire process. If the device becomes disconnected or unpaired from the machine during the process, the process will be terminated.
The user will be prompted to approve the action the first time the trigger is fired. Once the user grants permission to the trigger, he won't be prompted again. The permission runs on a per-app and per-device basis, so each new device will have to be granted permission before the background task can begin. In addition, only a single DeviceUseTrigger can run at a time for each app. If the app attempts a second trigger before the first one is complete, an exception will be thrown.
DeviceServicingTrigger
The DeviceServicingTrigger is a more heavyweight trigger. It's designed for things like firmware refreshes. As such, it has more capabilities than the DeviceUseTrigger. The DeviceServicingTrigger can run up to 30 minutes for each run. Unlike the DeviceUseTrigger, the device can become disconnected during the process. This allows for actions like device reboots during the process.
With elevated permissions comes a more controlled running policy. The user is prompted each time this trigger is fired and asked to grant permission. Similar to the DeviceUseTrigger, only a single DeviceServicingTrigger can run at a time. In fact, attempting to run a DeviceUseTrigger and a DeviceServicingTrigger at the same time will also result in an exception.
Geofencing and the LocationTrigger
Windows 8.1 includes a new concept known as "geofencing." Geofencing allows you to define a radius around a given location and receive notifications when the user enters or leaves that area. This creates a lot of interesting scenarios. For instance, you can set a reminder for the user when he returns home, so he can always remember to take out the trash. Another example would be to set geofences around tourist attractions to inform a user that he's near a point of interest.
To allow the user to be notified when a geofence is entered or left, a new LocationTrigger was added to the background tasks. Now your app can update the user even if the app is currently not running. This trigger type requires the app to be added to the lock screen in order to run LocationTrigger events.
What to Watch For
With only a few short months between the release of the Windows 8.1 Preview and the RTM, it's not likely you'll see much movement in the background APIs. As with many of the new features in the preview, the documentation on some of the new features is quite slim. However, you can expect to see more examples and more complete documentation available as the RTM release nears. The background APIs are an important aspect to developing Windows Store apps that need to run when an app is suspended. As such, I expect to see them become more efficient and more robust with each release.