Работа с фоновыми задачами в приложениях Windows

Замечание

В этой статье рассматриваются фоновые задачи, созданные с помощью API среда выполнения Windows (WinRT) BackgroundTaskBuilder в пространстве имен Windows.ApplicationModel.Background для приложений с удостоверением пакета, включая UWP и упакованные настольные приложения. Если вы создаете новое приложение или переносите существующее приложение в Windows App SDK, ознакомьтесь со статьей Использование фоновых задач в приложениях Windows и Стратегия миграции фоновых задач.

Узнайте, как создать и зарегистрировать фоновую задачу в приложении с помощью класса среда выполнения Windows (WinRT) BackgroundTaskBuilder.

Регистрация фоновой задачи

Полный пример регистрации фоновой задачи в приложении Universal Windows Platform (UWP) см. в примере backgroundTask.

В следующем примере показана регистрация задачи WIN32 COM, которая выполняется на повторяющейся 15-минутной таймере.

Чтобы зарегистрировать фоновую задачу, необходимо сначала создать новый экземпляр класса 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: идентификатор класса точки входа фоновой задачи.
  • taskName: имя фоновой задачи.

Метод RegisterBackgroundTaskWithSystem создает новый экземпляр класса и задает идентификатор класса триггера BackgroundTaskBuilder и точки входа для фоновой задачи. Затем метод регистрирует фоновую задачу в системе.

Замечание

Этот класс не поддерживает динамическую многопоточность, что означает, что необходимо учитывать его модель потоков и поведение маршалинга. Дополнительные сведения см. в разделе Threading and Marshaling (C++/CX) и Использование объектов среда выполнения Windows в многопоточной среде (.NET).

Управление современным режимом ожидания в фоновой задаче

BackgroundTaskBuilder и связанные API уже позволяют упакованным классическим приложениям выполнять фоновые задачи. Теперь API расширяет эти API, чтобы эти приложения могли выполнять код в современном резервном режиме. Обновление также добавляет свойства, которые могут запрашиваться приложением, чтобы определить, будет ли система регулировать фоновые задачи для приложения в современном режиме ожидания для экономии заряда батареи. Это позволяет создавать сценарии, в которых приложения получают вызовы VoIP или другие push-уведомления в режиме современного ожидания.

Замечание

"Упакованные настольные приложения" в этом разделе относятся к приложениям Win32 с идентификатором пакета (т. е. являются приложениями Desktop Bridge или Sparsely Signed Packaged) и имеют основную (или wmain) функцию в качестве точки входа.

В следующем примере показано, как разработчик приложения может использовать API BackgroundTaskBuilder для регистрации не более одной задачи с указанным именем задачи. В примере также показано, как проверить и зарегистрироваться на выполнение задачи в современном режиме ожидания для наиболее важных задач приложения.

// 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;
}

Ниже приведено поэтапное сквозное обновление следующего примера кода C++WinRT/C# на GitHub.

В примере показано, как вы можете использовать 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, фоновая задача не сможет получить доступ к сети в режиме ожидания подключения.