Condividi tramite


Uso delle attività in background nelle app di Windows

Scopri come creare e registrare un'attività in background nella tua app con windows Runtime (WinRT) classe BackgroundTaskBuilder.

Registrare un'attività in background

Vedi l'esempio BackgroundTask per un esempio completo di registrazione di un'attività in background in un'app UWP (Universal Windows Platform).

L'esempio seguente mostra la registrazione di un'attività COM Win32 eseguita su un timer ricorrente di 15 minuti.

Per registrare un'attività in background, è prima necessario creare una nuova istanza della classe BackgroundTaskBuilder. La classe BackgroundTaskBuilder viene usata per creare e registrare attività in background nell'app. Nell'esempio di codice seguente viene illustrato come creare una nuova istanza della classe 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);

Il metodo RegisterBackgroundTaskWithSystem accetta tre parametri:

  • trigger: trigger che avvierà l'attività in background.
  • entryPointClsid: ID classe del punto di ingresso dell'attività in background.
  • taskName: il nome dell'attività in background.

Il metodo RegisterBackgroundTaskWithSystem crea una nuova istanza della classe BackgroundTaskBuilder e imposta l'ID classe trigger e punto di ingresso per l'attività in background. Il metodo registra quindi l'attività in background con il sistema.

Nota

Questa classe non è agile, il che significa che è necessario considerare il modello di threading e il comportamento di marshaling. Per ulteriori informazioni, vedere Threading and Marshaling (C++/CX) e Utilizzo degli oggetti Windows Runtime in un ambiente multithread (.NET).

Gestire lo standby moderno in un'attività in background

Il BackgroundTaskBuilder e le API correlate consentono già alle applicazioni desktop in pacchetto di eseguire attività in background. L'API estende ora queste API per consentire a queste app di eseguire codice in standby moderno. L'aggiornamento aggiunge anche proprietà che possono essere interrogate da un'app per determinare se il sistema limiterà le attività in background per l'applicazione in modalità standby moderno per risparmiare la durata della batteria. Ciò consente scenari come le app che ricevono chiamate VoIP o altre notifiche push da modern standby.

Nota

"Applicazioni desktop in pacchetto" in questa sezione si riferisce alle applicazioni Win32 con identità del pacchetto (ad esempio, sono applicazioni Desktop Bridge o Sparse Signed Packaged) e hanno una funzione principale (o wmain) come punto di ingresso.

L'esempio seguente mostra come uno sviluppatore di app può usare l'API BackgroundTaskBuilder per registrare al massimo un'attività con il nome dell'attività specificato. L'esempio mostra anche come controllare e acconsentire esplicitamente alla registrazione dell'attività per l'esecuzione in standby moderno per le attività più critiche dell'applicazione.

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

Controllare se le attività in background hanno superato il budget in standby moderno

Il codice di esempio seguente illustra come uno sviluppatore di app può usare il BackgroundWorkCost.WasApplicationThrottledInStandby e BackgroundWorkCost.ApplicationEnergyUseLevel per monitorare e reagire quando le attività in background esauriscono il budget dell’app. Lo sviluppatore di app può reagire riducendo il lavoro con priorità inferiore svolto nello standby moderno. Si noti che si basa sul codice dell'esempio precedente.

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

Di seguito è riportato un aggiornamento end-to-end incrementale del codice di esempio C++WinRT/C# seguente in GitHub.

L'esempio mostra come utilizzare il BackgroundWorkCost.ApplicationEnergyUseTrend per monitorare la tendenza delle attività in background verso l'esaurimento del loro budget. È anche possibile interrompere l'esecuzione delle attività in background più costose durante lo standby moderno e impedire l'esecuzione di attività in background se l'app utilizza troppo rapidamente il proprio budget. Questo esempio si basa sul codice degli esempi precedenti.

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

Attività in background e connettività di rete

Se l'attività in background richiede la connettività di rete, tenere presenti le considerazioni seguenti.

  • Usare un SocketActivityTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di breve durata. Dopo aver eseguito l'attività, l'attività in background deve terminare per risparmiare energia.
  • Usare un ControlChannelTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di lunga durata.
  • Aggiungere la condizione InternetAvailable (BackgroundTaskBuilder.AddCondition) all'attività in background per posticiparne l'attivazione fino a quando lo stack di rete sia in esecuzione. Questa condizione consente di risparmiare energia perché l'attività in background non verrà eseguita fino a quando non sarà disponibile l'accesso alla rete. Questa condizione non fornisce l'attivazione in tempo reale.
  • Indipendentemente dal trigger usato, imposta IsNetworkRequested nell'attività in background per assicurarti che la rete rimanga attiva durante l'esecuzione dell'attività in background. Ciò indica all'infrastruttura delle attività in background di mantenere attiva la rete mentre l'attività è in esecuzione, anche se il dispositivo è entrato in modalità Standby connesso. Se l'attività in background non usa IsNetworkRequested, l'attività in background non sarà in grado di accedere alla rete in modalità Standby Connesso.