Non-suspending UWP Desktop Apps

Editor's note: The following post was written by Windows Development MVP Magnus Montin  as part of our Technical Tuesday series.  

A traditional Windows desktop application such as a .NET or a classic Win32 application is either running or non-running. This is not true for a Universal Windows Platform (UWP) app however. These kind of apps that run across all Windows 10 devices including phones, IoT devices and even Xbox consoles, enter an additional suspended state when being minimized or switched away from.

This makes perfect sense given the universal nature of a UWP app. Since it may be run on a small device with very limited hardware resources compared to a PC, a continuous execution of the app would cause rapid battery drain and steal resources and memory from other apps running on the same device. That's why all threads of the process are stopped and the memory used by the app may be wiped as well when the user switches to another foreground app.

Being able to run the very same app with the very same binaries on several different devices with different characteristics, such as screen sizes and input devices, may certainly be very appealing to you as a developer . at least if you build consumer apps. But maybe not so much if you are a traditional line-of-business (LOB) desktop application developer.

To give you an example, would you want to use Visual Studio to develop software on your mobile phone, using its touch screen as your primary and only input device? Would this ever make any sense? Hardly. Or think about running a multi-screen application with a lot of input controls on a HoloLens without a keyboard and a mouse. Would you - or rather your users - ever want to do that? Probably not.

The same way it hardly makes any sense for a traditional line-of-business desktop running on an always plugged-in full-blown desktop computer to ever get suspended by the operating system. So where does that leave UWP for LOB desktop developers?

Well, first there is a way to limit your UWP app to only one kind of device for example a PC. You do this by editing the app package manifest file (Package.appxmanifest) that Visual Studio creates for you when creating a Universal Windows project and change the Name of the TargetDeviceFamily element to "Windows.Desktop":

 <Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>

The valid target device family names can be found here.

Once you have done this, is there any way to bypass the UWP app lifecycle? After all, now that your app is constrained to run only on PCs, there is no reason for it to be suspended any more.

If you are developing an app that you don’t intend to publish in the Microsoft Store, you can prevent the app from being suspended by creating an ExtendedExecutionForegroundSession somewhere in your app, for example in your App.xaml.cs or bootstrapper class, and set its Reason property to ExtendedExecutionReason.Unconstrained:

 sealed partial class App : Application
{
    public App()
    {
        this.InitializeComponent();
    }

    protected override async void OnLaunched(LaunchActivatedEventArgs e)
    {
        Frame rootFrame = Window.Current.Content as Frame;
        if (rootFrame == null)
        {
            rootFrame = new Frame();
            Window.Current.Content = rootFrame;
        }

        if (e.PrelaunchActivated == false)
        {
            if (rootFrame.Content == null)
            {
                rootFrame.Navigate(typeof(MainPage), e.Arguments);
            }
            Window.Current.Activate();
        }

        if (_session == null)
            await PreventFromSuspending();
    }

    ExtendedExecutionForegroundSession _session;
    private async Task PreventFromSuspending()
    {
        ExtendedExecutionForegroundSession newSession = new ExtendedExecutionForegroundSession();
        newSession.Reason = ExtendedExecutionForegroundReason.Unconstrained;
        newSession.Revoked += SessionRevoked;

        ExtendedExecutionForegroundResult result = await newSession.RequestExtensionAsync();
        switch (result)
        {
            case ExtendedExecutionForegroundResult.Allowed:
                _session = newSession;
                break;
            default:
            case ExtendedExecutionForegroundResult.Denied:
                newSession.Dispose();
                break;
        }
    }

    private void SessionRevoked(object sender, ExtendedExecutionForegroundRevokedEventArgs args)
    {
        if (_session != null)
        {
            _session.Dispose();
            _session = null;
        }
    }
}

Make sure that you don't dispose the ExtendedExecutionForegroundSession. You also need to add the extendedBackgroundTaskTime and extendedExecutionUnconstrained restricted capabilities in the app's manifest in order to be able to get an ExtendedExecutionForegroundResult.Allowed result back from the RequestExtensionAsync() method:

 <Package xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
         xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
         xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
         xmlns:rescap="https://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
         IgnorableNamespaces="uap mp rescap">
  ...
  <Capabilities>
    <rescap:Capability Name="extendedBackgroundTaskTime" />
    <rescap:Capability Name="extendedExecutionUnconstrained" />
  </Capabilities>
</Package>

These restricted capabilities where introduced in the Windows Creators Update (version 1703) for LOB developers to be able to create apps that will run as long as needed regardless of the device's resource availability.

You can confirm that this works by copying the following code into the MainPage.xaml.cs file of a Blank App (Universal Windows) project that you create in Visual Studio 2017:

 

 public sealed partial class MainPage : Page
{
    private readonly TextBlock _textBlock = new TextBlock();

    public MainPage()
    {
        this.InitializeComponent();
        this.Content = _textBlock;
        App.Current.Suspending += Current_Suspending;
    }

    private void Current_Suspending(object sender, SuspendingEventArgs e) =>
        _textBlock.Text += $"Suspended at {DateTime.Now}{Environment.NewLine}";
}

If you minimize the app without creating the ExtendedExecutionForegroundSession above, you should note that the Suspending event is raised shortly afterwards:

If you however do call the PreventFromSuspending() method before minimizing the app, the Suspended event handler will never be invoked.

Note that you are not allowed to request access to the extendedExecutionUnconstrained capability in any app that is submitted to the store. It’s intended for enterprise Microsoft Store for Business apps and side-loaded LOB apps only.

Starting from the Anniversary update of Windows 10 (version 1607), you can however still prevent store apps from being suspended using almost the exact same code. Just replace the ExtendedExecutionForegroundSession with an ExtendedExecutionSession, set its Reason property to ExtendedExecutionReason.Unspecified and remove the restricted capabilities from the app manifest.

There is one caveat here though. If you run the store app on a laptop that is running on battery power, it may still get suspended unless the user has disabled the "Managed by Windows" option for the app. You find this option by opening the Settings app in Windows and go to System->Battery, click on the "Battery Usage by App" link and select the app in the "Battery usage by app" list. You may have to select the "1 week" and "All apps" options in the two combo boxes at the top for your app to show up in the filtered list:

Note that you should also check the “Allow app to run background tasks” checkbox. If you don’t, calling the RequestExtensionAsync() method of the ExtendedExecutionSession will always return a result of ExtendedExecutionResult.Denied which means that the app may still be suspended.

Also note that since you as a developer cannot actually control the user's settings, you may actually still want to handle the Suspending and Resuming events in apps that you submit to the store after all. At least if there is a possibility of your app being run on a laptop.

You may also want to handle the Revoked event of the ExtendedExecutionSession. If you deploy the app to the store, this event will be raised when there is a mandatory update available. The session will also be revoked when the operating system is rebooted.

Microsoft stated at the Build 2017 conference in May that UWP is the future for desktop applications on Windows. And being able to basically opt out of the application lifecycle management, or at least exclude the suspension of apps, by simply creating an ExtendedExecutionForegroundSession/ExtendedExecutionSession is certainly a step in the right direction.

In the latest releases of Windows 10 and the UWP we have also seen a lot of improvements and additions that certainly makes the platform even more appealing to LOB desktop developers, such as the ability to be able to activate an app from a command line, configure it to start at log-in and use of all the common APIs included in .NET Standard 2.0.


Magnus Montin is a senior client-facing IT consultant with several years’ hands-on experience of utilizing the Microsoft technology stack from front-to-back with exceptional C#, XAML and WPF skills, strong T-SQL skills and a thorough understanding of object orientated programming along with an appreciation of good software architecture and design patterns. He frequently reponds on the MSDN forums and has won several gold medals in the monthly MSDN TechNet Wiki Guru competition.