Praca z zadaniami w tle w aplikacjach Windows

Uwaga / Notatka

W tym artykule opisano zadania w tle utworzone za pomocą interfejsu API środowisko wykonawcze systemu Windows (WinRT) BackgroundTaskBuilder w przestrzeni nazw Windows.ApplicationModel.Background dla aplikacji z tożsamością pakietu, w tym aplikacji UWP i aplikacji klasycznych w pakiecie. Jeśli tworzysz nową aplikację lub migrujesz istniejącą aplikację do Zestaw SDK do aplikacji systemu Windows, zobacz Używanie zadań w tle w aplikacjach Windows i Strategia migracji zadań w tle.

Dowiedz się, jak utworzyć i zarejestrować zadanie w tle w aplikacji za pomocą klasy środowisko wykonawcze systemu Windows (WinRT) BackgroundTaskBuilder.

Zarejestruj zadanie w tle

Zobacz przykład BackgroundTask aby zapoznać się z kompletnym przykładem rejestrowania zadania w tle w aplikacji Universal Windows Platform (UWP).

W poniższym przykładzie pokazano rejestrację zadania Win32 COM uruchamianego na cyklicznym 15-minutowym czasomierzu.

Aby zarejestrować zadanie w tle, należy najpierw utworzyć nowe wystąpienie klasy BackgroundTaskBuilder . Klasa BackgroundTaskBuilder służy do tworzenia i rejestrowania zadań w tle w aplikacji. W poniższym przykładzie kodu pokazano, jak utworzyć nowe wystąpienie BackgroundTaskBuilder klasy:

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

Metoda RegisterBackgroundTaskWithSystem przyjmuje trzy parametry:

  • trigger: wyzwalacz, który uruchomi zadanie w tle.
  • entryPointClsid: identyfikator klasy punktu wejścia zadania w tle.
  • taskName: nazwa zadania w tle.

Metoda RegisterBackgroundTaskWithSystem tworzy nowe wystąpienie BackgroundTaskBuilder klasy i ustawia identyfikator klasy wyzwalacza i punktu wejścia dla zadania w tle. Następnie metoda rejestruje zadanie w tle w systemie.

Uwaga / Notatka

Ta klasa nie jest zwinna, co oznacza, że należy wziąć pod uwagę jej model wątkowy i zachowanie marshalingu. Aby uzyskać więcej informacji, zobacz Threading and Marshaling (C++/CX) i Za pomocą obiektów środowisko wykonawcze systemu Windows w środowisku wielowątkowym (.NET).

Obsługa trybu gotowości w zadaniu w tle

BackgroundTaskBuilder i powiązane interfejsy API umożliwiają spakowanym aplikacjom klasycznym uruchamianie zadań w tle. Interfejs API teraz rozszerza te możliwości, aby umożliwić aplikacjom wykonywanie kodu w trybie nowoczesnego uśpienia. Aktualizacja dodaje również właściwości, które mogą być odpytywane przez aplikację, aby określić, czy system ograniczy zadania w tle dla aplikacji w nowoczesnej trybie wstrzymania, aby zaoszczędzić żywotność baterii. Umożliwia to scenariusze takie jak aplikacje odbierające połączenia VoIP lub inne powiadomienia push z nowoczesnego trybu wstrzymania.

Uwaga / Notatka

"Spakowane aplikacje klasyczne" w tej sekcji odnosi się do aplikacji klasycznych Win32, które mają tożsamość pakietu (tj. są aplikacjami mostka klasycznego lub aplikacjami o małej objętości z podpisanym pakietem) i mają główną (lub wmain) funkcję jako punkt startowy.

W poniższym przykładzie pokazano, jak deweloper aplikacji może użyć interfejsu API BackgroundTaskBuilder do zarejestrowania co najwyżej jednego zadania o określonej nazwie zadania. W przykładzie pokazano również, jak sprawdzić i wybrać rejestrację zadań do uruchamiania w nowoczesnym trybie gotowości dla najważniejszych zadań aplikacji.

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

Sprawdź, czy zadania w tle przekroczyły budżet w nowoczesnej gotowości

Poniższy przykładowy kod pokazuje, jak deweloper aplikacji może używać elementu BackgroundWorkCost.WasApplicationThrottledInStandby i BackgroundWorkCost.ApplicationEnergyUseLevel, aby monitorować i reagować na wyczerpanie budżetu aplikacji w tle. Deweloper aplikacji może reagować, redukując zadania o niższym priorytecie w trybie nowoczesnej gotowości. Należy pamiętać, że opiera się to na kodzie z poprzedniego przykładu.

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

Poniżej przedstawiono przyrostową aktualizację do następującego przykładowego kodu C++WinRT/C# na GitHub.

W przykładzie pokazano, jak za pomocą BackgroundWorkCost.ApplicationEnergyUseTrend monitorować, jak zadania w tle zmierzają do wyczerpania swojego budżetu. Możesz również zatrzymać uruchamianie najdroższych zadań w tle oraz uniemożliwić uruchamianie tych zadań podczas nowoczesnej gotowości, jeśli aplikacja zbyt szybko wykorzystuje przeznaczony na nie budżet. Ten przykład opiera się na kodzie z poprzednich przykładów.

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

Zadania w tle i łączność sieciowa

Jeśli zadanie w tle wymaga łączności sieciowej, należy pamiętać o następujących kwestiach.

  • Użyj SocketActivityTrigger, aby aktywować zadanie w tle, gdy zostanie odebrany pakiet i należy wykonać zadanie krótkotrwałe. Po wykonaniu zadania zadanie w tle powinno zakończyć się w celu zaoszczędzenia zasilania.
  • Użyj obiektu ControlChannelTrigger, aby aktywować zadanie w tle po odebraniu pakietu, gdy musisz wykonać długotrwałe zadanie.
  • Dodaj warunek InternetAvailable (BackgroundTaskBuilder.AddCondition) do zadania w tle, aby opóźnić wyzwalanie zadania w tle do momentu uruchomienia stosu sieciowego. Ten warunek pozwala zaoszczędzić energię, ponieważ zadanie w tle nie zostanie wykonane, dopóki dostęp do sieci nie będzie dostępny. Ten warunek nie zapewnia aktywacji w czasie rzeczywistym.
  • Niezależnie od używanego wyzwalacza ustaw wartość IsNetworkRequested w zadaniu w tle, aby upewnić się, że sieć pozostaje uruchomiona podczas uruchamiania zadania w tle. To mówi infrastrukturze zadań w tle, aby utrzymywała połączenie sieciowe podczas wykonywania zadania, nawet jeśli urządzenie weszło w tryb podtrzymania połączenia. Jeśli zadanie w tle nie używa funkcji IsNetworkRequested, zadanie w tle nie będzie mogło uzyskać dostępu do sieci w trybie wstrzymania połączonego.