다음을 통해 공유



Special Windows 10 issue 2015

Volume 30 Number 11

App Lifecycle - Keep Apps Alive with Background Tasks and Extended Execution

By Shawn Henry | Windows 2015

It used to be the life of an application was easy. When a user launched an app, it could run wild on the system: consuming resources, popping up windows and generally doing as it pleased without regard for others. Nowadays, things are tougher. In a mobile-first world, apps are restricted to a defined application lifecycle. This lifecycle specifies when an app can run, and most of the time it can’t—if the app isn’t the current thing the user is doing, it’s not allowed to run.

In most cases, this is good—users know that an app isn’t consuming power or sapping performance. For apps, the OS is enforcing what has always been good practice, that applications enter a quiesced steady state when not in active use. This is especially important because the application model in the Universal Windows Platform (UWP) needs to scale from the lowest-end devices, like phones and Internet of Things (IoT) devices, all the way to the most powerful desktops and Xboxes.

For complex apps, the modern application model can seem restrictive at first, but as this article will describe, there are a few ways applications can expand the box and run (completing tasks and delivering notifications), even when not in the foreground.

The Application Lifecycle

In traditional Win32 and .NET desktop development, apps are typically in one of two states: “running” or “not running,” and most of the time they’re running. This may seem obvious, but think of an instant messaging application like Skype or a music app like Spotify: the user launches it, does some action (sending a message or searching for music), then goes off and does something else. All the while, Skype sits in the background waiting for messages to come in, and Spotify keeps playing music. This contrasts with modern apps (such as Windows apps built on the UWP), which spend most of their time in a state other than running.

Windows apps are always in one of three states: running, suspended or not running, as shown in Figure 1. When a user launches a Windows app, for example by tapping on a Tile on the Start menu, the app is activated and enters the running state. As long as the user is interacting with the app, it stays in the running state. If the user navigates away from the app or minimizes it, a suspended event is fired. This is an opportunity for the app to seri­alize any state it might need for when it’s resumed or re-activated, such as the current page of the application or partially filled-out form data. When an app is in the suspended state, its process and threads are suspended by Windows and can’t execute. When the user navigates back to the app, the application threads are unfrozen and the application resumes the running state.

The Windows Application Lifecycle
Figure 1 The Windows Application Lifecycle

If an application is in the suspended state but the resources it’s using (typically memory), are required for something else, such as running another app, the application is moved to the not running state.

From an app execution perspective, the difference between the suspended and not running states is whether the application is allowed to stay resident in memory. When an application is merely suspended, its execution is frozen, but all of its state information stays in memory. When an application isn’t running, it’s removed from memory. Because no event is fired when an app moves from suspended to not running, it’s important for an application to serialize all of the state information it needs from memory, in case it’s re-activated from not running.

Figure 2 shows what happens to resource usage as applications transition through the lifecycle. When an application is activated, it begins to consume memory, typically reaching a relatively stable plateau. When an application is suspended, its memory consumption typically goes down—buffers and used resources are released, and CPU consumption goes to zero (enforced by the OS). When an app moves from suspended to not running, both memory and CPU use go to zero, again enforced by the OS.

The Application Lifecycle and Resource Usage
Figure 2 The Application Lifecycle and Resource Usage

On many desktop systems with lots of RAM and large page files, it’s not common—but not impossible—for an application to be removed from memory, but this transition is much more common on mobile and other resource-constrained devices. For this reason, it’s important to test your UWP app on a wide variety of devices. The Windows Emulator that ships with Visual Studio can be immensely helpful for this; it allows developers to target devices with as little 512MB of memory.

Extended Execution

The application lifecycle I described is efficient—apps aren’t consuming resources if they aren’t being used—but it can result in a scenario where what an app needs can’t be done. For example, a typical use case for a social or communications app is to sign in and sync a bunch of cloud data (contacts, feeds, conversation history and so forth). With the basic application lifecycle as described, in order to download contacts, the user would need to keep the app open and in the foreground the entire time! Another example might be a navigation or fitness application that needs to track a user’s location. As soon as the user switched to a different app or put the device in their pocket, this would no longer work. There needs to be a mechanism to allow applications to run a bit longer.

The Universal Windows Platform introduces the concept of extended execution to help with these types of scenarios. There are two cases where extended execution can be used:

  1. At any point during regular foreground execution, while the application is in the running state.
  2. After the application has received a suspending event (the OS is about to move the app to the suspended state) in the application’s suspending event handler.

The code for these two cases is the same, but the application behaves a little differently in each. In the first case, the application stays in the running state, even if an event that normally would trigger suspension occurs (for example, the user navigating away from the application). The application will never receive a suspending event while the execution extension is in effect. When the extension is disposed, the application becomes eligible for suspension again.

With the second case, if the application is transitioning to the suspended state, it will stay in a suspending state for the period of the extension. Once the extension expires, the application enters the suspended state without further notification.

Figure 3 shows how to use extended execution to extend the uspending state of an application. First, a new ExtendedExecution­Session is created and supplied with a Reason and Description. These two properties are used to allocate the correct resource set for that application (that is, the amount of memory, CPU and execution time it’s allowed) and to expose information to the user about what applications are doing in the background. The application then hooks up a revocation event handler, which is called if Windows can no longer support the extension, for example, if another high-priority task, like a foreground application or an incoming VoIP call, needs the resources. Finally, the application requests the extension and, if successful, begins its save operation. If the extension is denied, the application performs a suspension operation as if it hadn’t received the extension, like a regular suspension event.

Figure 3 Extended Execution in the OnSuspending Handler

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
  var deferral = e.SuspendingOperation.GetDeferral();
  using (var session = new ExtendedExecutionSession())
  {
    session.Reason = ExtendedExecutionReason.SavingData;
    session.Description = "Upload Data";
    session.Revoked += session_Revoked;
    var result = await session.RequestExtensionAsync();
    if (result == ExtendedExecutionResult.Denied)
    {
      UploadBasicData();
    }
    // Upload Data
    var completionTime = await UploadDataAsync(session);
  }
  deferral.Complete();
}

Figure 4 shows the impact this has on the app lifecycle; compare it to Figure 2. When an application gets a suspension event, it begins to release or serialize resources. If the app determines it needs more time and takes an extension, it remains in the suspending state until the extension is revoked.

Resource Usage During Extended Execution
Figure 4 Resource Usage During Extended Execution

As mentioned earlier in case 2, execution extension doesn’t have to be requested only in the suspension handler; it can be requested at any point while the application is in the running state. This is useful for when the application knows beforehand it will need to continue running in the background, as with the navigation app mentioned earlier. The code is very similar to the previous example, and is shown in Figure 5.

Figure 5 Extended Execution During Regular Execution

private async void StartTbTNavigationSession()
{
  using (var session = new ExtendedExecutionSession())
  {
    session.Reason = ExtendedExecutionReason.LocationTracking;
    session.Description = "Turn By Turn Navigation";
    session.Revoked += session_Revoked;
    var result = await session.RequestExtensionAsync();
    if (result == ExtendedExecutionResult.Denied
    {
      ShowUserWarning("Background location tracking not available");
    }
    // Do Navigation
    var completionTime = await DoNavigationSessionAsync(session);
  }
}

Figure 6 shows the application lifecycle for this scenario. Again, it’s very similar; the difference is that when the execution extension is revoked, the application will likely quickly transition through the suspended state into the not running state. This happens because, in this case, the extension is typically revoked only due to resource pressure, a situation that can only be mitigated by releasing resources (that is, removing the app from memory).

Resource Usage During Extended Execution
Figure 6 Resource Usage During Extended Execution

Background Tasks

There’s another way an app can run in the background, and that’s as a background task. Background tasks are separate components in an application that implement the IBackgroundTask interface. These components can be executed without heavyweight UI frameworks, and typically execute in a separate process (although they can also be run in-proc with the primary application executable).

A background task is executed when its associated trigger is fired. A trigger is a system event that can fire and activate an app, even if the app isn’t running. For example, the TimeTrigger can be generated with a specific time interval (say, every 30 minutes), in which case the application’s background task would activate every 30 minutes when the trigger fires. There are many trigger types supported by Windows, including these background trigger types: TimeTrigger, PushNotificationTrigger, LocationTrigger, ContactStoreNotificationTrigger, BluetoothLEAdvertisementWatcherTrigger, UserPresent, InternetAvailable and PowerStateChange.

Using a background task is a three-step process: The component needs to be created, then declared in the application manifest and then registered at run time.

Background tasks are typically implemented in separate Windows Runtime (WinRT) component projects in the same solution as the UI project. This allows the background task to be activated in a separate process, reducing the memory overhead required by the component. A simple implementation of an IBackgroundTask is shown in Figure 7. IBackgroundTask is a simple interface that defines just one method, Run. This is the method that’s called when the background task’s trigger is fired. The method’s only parameter is an IBackgroundTaskInstance object that contains context about the activation (for example, the payload of an associated push notification or the action used by a toast notification) and event handlers to handle lifecycle events such as cancellation. When the Run method completes, the background task is terminated. For this reason, just as in the suspension handler shown earlier, it’s important to use the deferral object (also hanging off the IBackgroundTaskInstance) if your code is asynchronous.

Figure 7 A BackgroundTask Implementation

public sealed class TimerTask : IBackgroundTask
{
  public void Run(IBackgroundTaskInstance taskInstance)
  {
    var deferral = taskInstance.GetDeferral();
    taskInstance.Canceled += TaskInstance_Canceled;
    await ShowToastAsync("Hello from Background Task");
    deferral.Complete();
  }
  private void TaskInstance_Canceled(IBackgroundTaskInstance sender,
    BackgroundTaskCancellationReason reason)
  {
    // Handle cancellation
    deferral.Complete();
  }
}

In the application manifest, the background task must also be registered. This registration tells Windows the trigger type, the entry point and the executable host of the task, as follows: 

<Extensions>
  <Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTasks.TimerTask">
    <BackgroundTasks>
      <Task Type="timer" />
    </BackgroundTasks>
  </Extension>

This can also be done without diving into XML by using the manifest designer included with Visual Studio.

Finally, the task must also be registered at run time, and this is shown in Figure 8. Here I use the BackgroundTaskBuilder object to register the task with a TimeTrigger that will fire every 30 minutes, if the Internet is available. This is the ideal type of trigger for common operations like updating a tile or periodically syncing small amounts of data.

Figure 8 Background Task Registration

private void RegisterBackgroundTasks()
{
  BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
  builder.Name = "Background Test Class";
  builder.TaskEntryPoint = "BackgroundTaskLibrary.TestClass";
  IBackgroundTrigger trigger = new TimeTrigger(30, true);
  builder.SetTrigger(trigger);
  IBackgroundCondition condition =
    new SystemCondition(SystemConditionType.InternetAvailable);
  builder.AddCondition(condition);
  IBackgroundTaskRegistration task = builder.Register();
  task.Progress += new BackgroundTaskProgressEventHandler(task_Progress);
  task.Completed += new BackgroundTaskCompletedEventHandler(task_Completed);
}

The biggest advantage of background tasks can also be their curse: Because background tasks run in the background (when the user might be doing something important in the foreground or the device is in standby), they are tightly restricted in the amount of memory and CPU time they can use. For example, the background task registered in Figure 8 will run for only 30 seconds and can’t consume more than 16MB of memory on devices with 512MB of memory; the memory caps scale with the amount of memory on the device. When developing and implementing background tasks, it’s important to take this into consideration. Background tasks should be tested on a variety of devices, especially low-end devices, before you publish an application. There are a couple of other things to note, as well:

  • If Battery Saver is available and active (typically when the battery is below a certain charge threshold), background tasks are prevented from running until the battery is recharged past the battery-saver threshold.
  • On previous versions of Windows, applications needed to be “pinned” to the lock before they were allowed to execute in the background and, in some cases, there were a maximum number of background tasks that could be registered, device-wide. This is no longer the case in Windows 10, but an app must always call BackgroundExecutionManger.RequestAcessAsync to declare its intent to run in the background.

A Better Way to Do Contact Syncing

There are many background operations that can be completed with either extended execution or with background tasks. Typically, it’s better to use background tasks if possible—they’re more reliable and efficient. 

For example, the scenario mentioned earlier—syncing data when an app is first launched—can also be done, and done more efficiently, with a background task, in this case using a trigger that’s new to Windows 10: the application trigger.

ApplicationTrigger (like DeviceUseTrigger) belongs to a special class of triggers that are triggered directly from the foreground portion of the application. This is done by explicitly calling RequestAsync on the trigger object. ApplicationTrigger is particularly useful for scenarios where the application wants to start an operation in the foreground that should opportunistically continue in the background if the application is no longer in the foreground, and that operation isn’t dependent on being tightly coupled with the foreground. The following shows an example of an Application­Trigger task that can be used in place of extended execution for most scenarios:

var appTrigger = new ApplicationTrigger();
var backgroundTaskBuilder = new BackgroundTaskBuilder();
backgroundTaskBuilder.Name = "App Task";
backgroundTaskBuilder.TaskEntryPoint = typeof(AppTask).FullName;
backgroundTaskBuilder.SetTrigger(appTrigger);
backgroundTaskBuilder.Register();
var result = await appTrigger.RequestAsync();

Wrapping Up

This article gives an overview of the application lifecycle and background execution in Windows 10 and introduces a couple of new mechanisms that apps can use to run in the background: extended execution and background tasks. Background tasks are a better choice for low-end and memory-constrained devices (like phones), whereas extended execution is more appropriate for higher-end devices (like desktop PCs).


Shawn Henry is a Senior Program Manager on the Universal Windows Platform team. Reach him on Twitter: @shawnhenry.

Thanks to the following Microsoft technical experts for reviewing this article: Anis Mohammed Khaja Mohideen and Abdul Hadi Sheikh
Abdul Hadi Sheikh is a Software Developer on the Universal Windows Platform team

Anis Mohammed Khaja Mohideen is a Senior Software Developer on Universal Windows Platform team