语言

在 Windows 应用中处理后台任务

注意

本文介绍构建在拥有包标识的应用中的后台任务,这些任务是通过 Windows.ApplicationModel.Background 命名空间中的 Windows 运行时 (WinRT) BackgroundTaskBuilder API 生成的,包括 UWP 和已打包的桌面应用。 如果要生成新应用或将现有应用迁移到 Windows 应用 SDK,请参阅在 Windows 应用中使用后台任务和 Background 任务迁移策略。

了解如何在应用中使用 Windows 运行时 (WinRT) BackgroundTaskBuilder 类创建和注册后台任务。

注册后台任务

有关在 通用 Windows 平台 (UWP) 应用中注册后台任务的完整示例,请参阅 BackgroundTask 示例。

以下示例演示在重复的 15 分钟计时器上运行的 Win32 COM 任务的注册。

若要注册后台任务,必须先创建 BackgroundTaskBuilder 类的新实例。 BackgroundTaskBuilder 类用于在应用中创建和注册后台任务。 下面的代码示例演示如何创建 BackgroundTaskBuilder 类的新实例:

using System;
using Windows.ApplicationModel.Background;

public IBackgroundTaskRegistration RegisterBackgroundTaskWithSystem(IBackgroundTrigger trigger, Guid entryPointClsid, string taskName)
{
    BackgroundTaskBuilder builder = new BackgroundTaskBuilder();

    builder.SetTrigger(trigger);
    builder.SetTaskEntryPointClsid(entryPointClsid);

    BackgroundTaskRegistration registration;
    if (builder.Validate())
    {
        registration = builder.Register(taskName);
    }
    else
    {
        registration = null;
    }

    return registration;
}

RegisterBackgroundTaskWithSystem(new TimeTrigger(15, false), typeof(TimeTriggeredTask).GUID, typeof(TimeTriggeredTask).Name);

RegisterBackgroundTaskWithSystem 方法采用三个参数:

  • trigger:将启动后台任务的触发器。
  • entryPointClsid:后台任务入口点的类 ID。
  • taskName:后台任务的名称。

RegisterBackgroundTaskWithSystem 方法创建 BackgroundTaskBuilder 类的新实例,并为后台任务设置触发器和入口点类 ID。 然后,该方法向系统注册后台任务。

注意

此类不是敏捷的,这意味着你需要考虑其线程模型和封送处理行为。 有关详细信息,请参阅线程和封送处理(C++/CX)和在多线程环境中使用 Windows 运行时 对象(.NET)。

在后台任务中处理现代待机

BackgroundTaskBuilder 和相关 API 已允许打包的桌面应用程序运行后台任务。 API 现在扩展了这些 API,使这些应用能够在新式待机中执行代码。 更新还会添加应用可以查询的属性,以确定系统是否会限制新式待机中应用程序的后台任务,以节省电池使用时间。 这使得应用程序能够在新式待机模式下接收 VoIP 呼叫或其他推送通知。

注意

本节中的“打包桌面应用程序”是指具有包标识的 Win32 应用程序(即桌面桥或稀疏签名的打包应用程序),并且以 main(或 wmain)函数作为其入口点。

以下示例演示应用开发人员如何使用 BackgroundTaskBuilder API 将最多一个任务注册到指定任务名称。 此示例还演示了如何检查并在应用程序的关键任务中选择在现代待机模式下启用任务登记。

// The following namespace is required for BackgroundTaskBuilder APIs. 
using Windows.ApplicationModel.Background; 

// The following namespace is required for API version checks. 
using Windows.Foundation.Metadata; 

// The following namespace is used for showing Toast Notifications. This 
// namespace requires the Microsoft.Toolkit.Uwp.Notifications NuGet package 
// version 7.0 or greater. 
using Microsoft.Toolkit.Uwp.Notifications; 

// Incoming calls are considered to be critical tasks to the operation of the app. 
const string IncomingCallTaskName = "IncomingCallTask"; 
const string NotificationTaskName = "NotificationTask"; 
const string PrefetchTaskName = "PrefetchTask"; 

public static bool IsAllowedInBackground(BackgroundAccessStatus status) { 
    return ((status != BackgroundAccessStatus.Denied) && 
            (status != BackgroundAccessStatus.DeniedBySystemPolicy) && 
            (status != BackgroundAccessStatus.DeniedByUser) && 
            (status != BackgroundAccessStatus.Unspecified)); 
} 

public async void RegisterTask(IBackgroundTrigger trigger, 
                               Guid entryPointClsid, 
                               string taskName, 
                               bool isRunInStandbyRequested) 
{ 
    var taskBuilder = new BackgroundTaskBuilder(); 
    taskBuilder.SetTrigger(trigger); 
    taskBuilder.SetTaskEntryPointClsid(entryPointClsid); 

    // Only the most critical background work should be allowed to proceed in 
    // modern standby. Additionally, some platforms may not support modern 
    // or running background tasks in modern standby at all. Only attempt to 
    // request modern standby execution if both are true. Requesting network 
    // is necessary when running in modern standby to handle push notifications. 
    if (IsRunInStandbyRequested && taskBuilder.IsRunningTaskInStandbySupported) 
    { 
        var accessStatus = BackgroundExecutionManager.GetAccessStatusForModernStandby(); 
        if (!IsAllowedInBackground(accessStatus) 
        { 
            await BackgroundExecutionManager.RequestAccessKindForModernStandby( 
                    BackgroundAccessRequestKind.AllowedSubjectToSystemPolicy, 
                    "This app wants to receive incoming notifications while your device is asleep"); 
        } 

        accessStatus = BackgroundExecutionManager.GetAccessStatusForModernStandby(); 

        if (IsAllowedInBackground(accessStatus) 
        { 
            taskBuilder.IsRunningTaskInStandbyRequested = true; 
            taskBuilder.IsNetworkRequested = true; 
        } 
    } 

    // Check that the registration is valid before attempting to register. 
    if (taskBuilder.IsRegistrationValid) 
    { 
        // If a task with the specified name already exists, it is unregistered 
        // before a new one is registered. Note this API may still fail from 
        // catastrophic failure (e.g., memory allocation failure). 
        taskBuilder.Register(taskName); 
    } 

    return; 
} 

RegisterTask(new PushNotificationTrigger(), "{INSERT-YOUR-GUID-HERE}", IncomingCallTaskName, true); 

检查后台任务是否在现代待机中超出了其预算

以下示例代码演示应用开发人员如何使用 BackgroundWorkCost.WasApplicationThrottledInStandby 和 BackgroundWorkCost.ApplicationEnergyUseLevel 监视和响应其后台任务耗尽其应用预算。 应用开发人员可以通过减少新式待机中完成的优先级较低的工作来做出反应。 请注意,这依赖于上一示例中的代码。

public async void ReduceBackgroundCost() 
{ 
    BackgroundTaskRegistration callTask; 
    BackgroundTaskRegistration notificationTask; 
    BackgroundTaskRegistration prefetchTask; 

    // Nothing to do if the app was not or will not be throttled. 
    if (!BackgroundWorkCost.WasApplicationThrottledInStandby && 
        (BackgroundWorkCost.ApplicationEnergyUseLevel != StandbyEnergyUseLevel.OverBudget)) 
    { 
        return; 
    } 

    foreach (var task in BackgroundTaskRegistration.AllTasks) 
    { 
        switch (task.Value.Name) { 
        case IncomingCallTaskName: 
            callTask = task.Value; 
            break; 

        case NotificationTaskName: 
            notificationTask = task.Value; 
            break; 

        case PrefetchTaskName: 
            prefetchTask = task.Value; 
            break; 

        default: 
        } 
    } 

    if (callTask.WasTaskThrottledInStandby) 
    { 
        // Unset the throttle flag after acknowledging it so the app can 
        // react to the same task being throttled again in the future. 
        task.Value.WasTaskThrottledInStandby = false; 

        // Notify the user that the notification was missed. 
        new ToastContentBuilder() 
            .AddText("You missed a call") 
            .AddText(task.Value.Name) 
            .Show(); 

        // Because the incoming calls were not activated, demote less notifications 
        // tasks so the calls can be delivered promptly in the future. 
        RegisterTask(notificationTask.Value.Trigger, 
                     typeof(TimeTriggeredTask).GUID, 
                     notificationTask.Value.Name, 
                     false); 
    } 

    // Note that if incoming call tasks were throttled in some previous modern 
    // standby session, the application energy use was over budget for some period. 
    // Demote unimportant tasks like prefetch work to avoid calls and notifications 
    // from being throttled.
    if (callTask.WasTaskThrottledInStandby) ||
        (BackgroundWorkCost.ApplicationEnergyUseLevel == StandbyEnergyUseLevel.OverBudget))
    {
        RegisterTask(prefetchTask.Value.Trigger,
                     typeof(TimeTriggeredTask).GUID,
                     prefetchTask.Value.Name,
                     false);
    }

    return;
}

下面是对 GitHub 的以下 C++WinRT/C# 示例代码的增量端到端更新。

该示例演示如何使用 BackgroundWorkCost.ApplicationEnergyUseTrend 监视后台任务如何耗尽其预算。 你还可以阻止最昂贵的后台任务在现代待机中运行,并防止后台任务在现代待机中运行,如果它们的应用使用预算过快。 此示例依赖于前面示例中的代码。

public async void ReduceBackgroundCostPreemptively() 
{ 
    BackgroundTaskRegistration mostExpensiveTask = null; 

    // We can't do anything preemptively since the trend isn't known. 
    if (!BackgroundWorkCost.IsApplicationEnergyUseTrendKnown) 
    { 
        return; 
    } 

    // The app is not trending towards being over budget, so this method can 
    // return early. 
    if ((BackgroundWorkCost.ApplicationEnergyUseTrend != EnergyUseTrend.OverBudget) && 
        (BackgroundWorkCost.ApplicationEnergyUseTrend != EnergyUseTrend.OverHalf)) 
    { 
        return; 
    } 

    // The application is going exceeding its budget very quickly. Demote the 
    // most expensive task that is not the call task before call tasks start being 
    // throttled. 
    if (BackgroundWorkCost.ApplicationEnergyUseTrend == EnergyUseTrend.OverBudget) 
    { 
        foreach (var task in BackgroundTaskRegistration.AllTasks) 
        { 
            if ((task.Value.Name != IncomingCallTaskName) && 
                ((mostExpensiveTask == null) || 
                 (mostExpensiveTask.ApplicationEnergyUseTrendContributionPercentage < 
                  task.Value.ApplicationEnergyUseTrendContributionPercentage))) 
            { 
                mostExpensiveTask = task.Value; 
            } 
        } 
    } 

    if (mostExpensiveTask != null) 
    { 
        RegisterTask(mostExpensiveTask.Trigger, 
                     typeof(TimeTriggeredTask).GUID, 
                     mostExpensiveTask.Name, 
                     false); 
    } 

    // The application is trending toward eventually exceeding its budget. Demote the 
    // least important prefetch task before calls and notifications are throttled. 
    foreach (var task in BackgroundTaskRegistration.AllTasks) 
    { 
        if (task.Value.Name == PrefetchTaskName) { 
            RegisterTask(task.Value.Trigger, 
                         typeof(TimeTriggeredTask).GUID, 
                         task.Value.Name, 
                         false); 
        } 
    } 

    return; 
} 

后台任务和网络连接

如果后台任务需要网络连接,请注意以下注意事项。

  • 在检索到程序包并且需要执行生存期较短的任务时,使用 SocketActivityTrigger 激活后台任务。 执行任务后,后台任务应终止以节省电源。
  • 在检索到程序包并且需要执行生存期较长的任务时,使用 ControlChannelTrigger 激活后台任务。
  • 将 InternetAvailable 条件(BackgroundTaskBuilder.AddCondition)添加到后台任务,以延迟触发后台任务,直到网络堆栈正在运行。 此条件可节省电源,因为在网络访问可用之前,后台任务不会执行。 此条件不提供实时激活。
  • 无论使用哪种触发器,在后台任务上设置 IsNetworkRequested,以确保后台任务运行时网络保持正常运行。 这会告知后台任务基础结构在执行任务时保持网络状态,即使设备已进入连接待机模式也是如此。 如果后台任务不使用 IsNetworkRequested,则当处于连接待机模式时,后台任务将无法访问网络。