Trabalhando com tarefas em segundo plano em aplicativos Windows

Observação

Este artigo aborda tarefas em segundo plano criadas com a API Windows Runtime BackgroundTaskBuilder no Windows.ApplicationModel.Background namespace para apps com identidade de pacote, incluindo UWP e apps de desktop empacotados. Se você estiver criando um novo aplicativo ou migrando um aplicativo existente para o SDK do Aplicativo Windows, consulte Usando tarefas em segundo plano em aplicativos Windows e Estratégia de migração de tarefas em segundo plano.

Saiba como criar e registrar uma tarefa em segundo plano em seu aplicativo com a classe Windows Runtime (WinRT) BackgroundTaskBuilder.

Registrar uma tarefa em segundo plano

Consulte o exemplo BackgroundTask para obter um exemplo completo de registro de uma tarefa em segundo plano em um aplicativo Plataforma Universal do Windows (UWP).

O exemplo a seguir mostra o registro de uma tarefa COM do Win32 que é executada em um temporizador recorrente de 15 minutos.

Para registrar uma tarefa em segundo plano, primeiro você deve criar uma nova instância da classe BackgroundTaskBuilder . A BackgroundTaskBuilder classe é usada para criar e registrar tarefas em segundo plano em seu aplicativo. O exemplo de código a seguir demonstra como criar uma nova instância da BackgroundTaskBuilder classe:

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);

O RegisterBackgroundTaskWithSystem método usa três parâmetros:

  • trigger: o gatilho que iniciará a tarefa em segundo plano.
  • entryPointClsid: o ID da classe do ponto de entrada da tarefa de segundo plano.
  • taskName: O nome da tarefa em segundo plano.

O método RegisterBackgroundTaskWithSystem cria uma nova instância da classe BackgroundTaskBuilder e define o ID de classe de gatilho e ponto de entrada para a tarefa em segundo plano. Em seguida, o método registra a tarefa em segundo plano com o sistema.

Observação

Essa classe não é ágil, o que significa que você precisa considerar seu modelo de encadeamento e comportamento de marshalização. Para obter mais informações, consulte Threading and Marshaling (C++/CX) e Usando objetos Windows Runtime em um ambiente multithread (.NET).

Manipular o modo de espera moderno em uma tarefa em segundo plano

O BackgroundTaskBuilder e as APIs relacionadas já permitem que aplicativos de área de trabalho empacotados executem tarefas em segundo plano. A API agora estende essas APIs para permitir que esses aplicativos executem código em espera moderno. A atualização também adiciona propriedades que podem ser consultadas por um aplicativo para determinar se o sistema restringirá as tarefas em segundo plano do aplicativo em modo de espera moderno para conservar a vida útil da bateria. Isso permite cenários como aplicativos que recebem chamadas VoIP ou outras notificações push do modo de espera moderno.

Observação

"Aplicativos de área de trabalho empacotados" nesta seção refere-se a aplicativos Win32 que possuem identidade de pacote (ou seja, são aplicativos empacotados como Desktop Bridge ou como pacotes esparsos assinados) e têm uma função principal (ou wmain) como ponto de entrada.

O exemplo a seguir mostra como um desenvolvedor de aplicativos pode usar a API BackgroundTaskBuilder para registrar no máximo uma tarefa com o nome da tarefa especificado. O exemplo também mostra como verificar e optar pelo registro de tarefas para serem executadas em modo de espera moderno nas tarefas mais críticas do aplicativo.

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

Verificar se as tarefas em segundo plano excederam seu orçamento no Modern Standby

O código de exemplo a seguir mostra como um desenvolvedor de aplicativos pode usar o BackgroundWorkCost.WasApplicationThrottledInStandby e BackgroundWorkCost.ApplicationEnergyUseLevel para monitorar e reagir a fazer com que suas tarefas em segundo plano esgote o orçamento do aplicativo. O desenvolvedor do aplicativo pode reagir reduzindo o trabalho de menor prioridade realizado durante a espera moderna. Observe que isso depende do código do exemplo anterior.

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

Veja a seguir uma atualização incremental de ponta a ponta para o seguinte código de exemplo C++WinRT/C# em GitHub.

O exemplo mostra como você pode usar o BackgroundWorkCost.ApplicationEnergyUseTrend para monitorar como suas tarefas em segundo plano têm a tendência de esgotar seu orçamento. Você também pode interromper as tarefas em segundo plano mais caras de serem executadas no modo standby moderno e evitar que outras tarefas sejam executadas dessa forma, caso o aplicativo esteja utilizando seu orçamento rapidamente. Este exemplo depende do código de exemplos anteriores.

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

Tarefas em segundo plano e conectividade de rede

Se sua tarefa em segundo plano exigir conectividade de rede, esteja ciente das considerações a seguir.

  • Use um SocketActivityTrigger para ativar a tarefa em segundo plano quando um pacote é recebido e você precisa executar uma tarefa de curta duração. Depois de executar a tarefa, a tarefa em segundo plano deve ser encerrada para economizar energia.
  • Use um ControlChannelTrigger para ativar a tarefa em segundo plano quando um pacote é recebido e você precisa executar uma tarefa de longa duração.
  • Adicione a condição InternetAvailable (BackgroundTaskBuilder.AddCondition) à sua tarefa em segundo plano para atrasar o acionamento da tarefa até que a pilha de rede esteja em execução. Essa condição economiza energia porque a tarefa em segundo plano não será executada até que o acesso à rede esteja disponível. Essa condição não fornece ativação em tempo real.
  • Independentemente do gatilho usado, defina IsNetworkRequested em sua tarefa em segundo plano para garantir que a rede permaneça ativa enquanto a tarefa em segundo plano é executada. Isso informa a infraestrutura de tarefas em segundo plano para manter a rede ativa enquanto a tarefa está em execução, mesmo que o dispositivo tenha entrado no modo Connected Standby. Se sua tarefa em segundo plano não usar IsNetworkRequested, sua tarefa em segundo plano não poderá acessar a rede quando estiver no modo De espera conectado.