Sdílet prostřednictvím


Práce s úlohami na pozadí v aplikacích pro Windows

Zjistěte, jak ve své aplikaci vytvořit a zaregistrovat úlohu na pozadí pomocí třídy Windows Runtime (WinRT) BackgroundTaskBuilder.

Registrace úlohy na pozadí

Kompletní příklad registrace úlohy na pozadí v aplikaci pro Univerzální platformu Windows (UPW) najdete v ukázce BackgroundTask.

Následující příklad ukazuje registraci Win32 COM úlohy, která se spouští každých 15 minut.

Chcete-li zaregistrovat úlohu na pozadí, musíte nejprve vytvořit novou instanci třídy BackgroundTaskBuilder. Třída BackgroundTaskBuilder slouží k vytváření a registraci úloh na pozadí ve vaší aplikaci. Následující příklad kódu ukazuje, jak vytvořit novou instanci BackgroundTaskBuilder třídy:

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 přebírá tři parametry:

  • trigger: Trigger, který spustí úlohu na pozadí.
  • entryPointClsid: ID třídy vstupního bodu pro úlohu na pozadí.
  • taskName: Název úlohy na pozadí.

Metoda RegisterBackgroundTaskWithSystem vytvoří novou instanci třídy BackgroundTaskBuilder a nastaví ID třídy triggeru a vstupního bodu pro úlohu na pozadí. Metoda pak zaregistruje úlohu na pozadí v systému.

Poznámka

Tato třída není agilní, což znamená, že je potřeba zvážit její model vláken a chování při zprostředkování. Další informace najdete v tématech Vlákna a maršálování (C++/CX) a Použití objektů Windows Runtime ve vícevláknovém prostředí (.NET).

Správa moderního standby režimu v úloze na pozadí

Rozhraní API BackgroundTaskBuilder a související rozhraní API již umožňují zabaleným desktopovým aplikacím spouštět úlohy na pozadí. Rozhraní API nyní rozšiřuje podporu těchto rozhraní, aby aplikace mohly spouštět kód v moderním pohotovostním režimu. Aktualizace také přidává vlastnosti, na které se může aplikace dotazovat, aby zjistila, jestli systém omezí úlohy na pozadí aplikace v moderním pohotovostním režimu, aby se ušetřila výdrž baterie. To umožňuje scénáře, jako jsou aplikace, které přijímají volání VoIP nebo jiná nabízená oznámení z moderního pohotovostního režimu.

Poznámka

"Balíčkové desktopové aplikace" v této části odkazují na aplikace Win32, které mají identitu balíčku (tj. Desktop Bridge nebo Sparse Signed Packaged aplikace) a mají hlavní (nebo wmain) funkci jako vstupní bod.

Následující příklad ukazuje, jak vývojář aplikace může použít rozhraní API BackgroundTaskBuilder k registraci maximálně jedné úlohy se zadaným názvem úlohy. Ukázka také ukazuje, jak zkontrolovat a přihlásit registraci úlohy tak, aby běžela v moderním pohotovostním režimu pro nejdůležitější úlohy aplikace.

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

Kontrola, jestli úkoly na pozadí překročily rozpočet v moderním pohotovostním režimu

Následující ukázkový kód ukazuje, jak může vývojář aplikace využít BackgroundWorkCost.WasApplicationThrottledInStandby a BackgroundWorkCost.ApplicationEnergyUseLevel ke sledování a reakci na to, že jejich úlohy na pozadí vyčerpají rozpočet aplikace. Vývojář aplikací může reagovat snížením práce s nižší prioritou v moderním pohotovostním režimu. Všimněte si, že se spoléhá na kód z předchozí ukázky.

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

Následuje přírůstková end-to-end aktualizace pro vzorový kód C++WinRT/C# na GitHubu.

Příklad ukazuje, jak můžete použít BackgroundWorkCost.ApplicationEnergyUseTrend ke sledování, jak úkoly na pozadí postupně vyčerpávají svůj rozpočet. Můžete také zastavit nejnákladnější úlohy na pozadí v moderním pohotovostním režimu a zabránit tomu, aby úlohy na pozadí běžely v moderním pohotovostním režimu, pokud jejich aplikace používá rozpočet příliš rychle. Tato ukázka spoléhá na kód z předchozích ukázek.

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

Úlohy na pozadí a síťové připojení

Pokud vaše úloha na pozadí vyžaduje síťové připojení, mějte na paměti následující aspekty.

  • Pomocí SocketActivityTrigger aktivujte úkol na pozadí při přijetí paketu, když potřebujete provést krátkodobý úkol. Po provedení úkolu by se úloha na pozadí měla ukončit, aby se ušetřila energie.
  • Pomocí ControlChannelTrigger aktivujte úlohu na pozadí při přijetí paketu, když potřebujete provést dlouho trvající úlohu.
  • Přidejte do úlohy na pozadí podmínku InternetAvailable (BackgroundTaskBuilder.AddCondition), která zpozdí aktivaci úlohy na pozadí, dokud se zásobník sítě nespustí. Tato podmínka šetří energii, protože úloha na pozadí se nespustí, dokud nebude k dispozici přístup k síti. Tato podmínka neposkytuje aktivaci v reálném čase.
  • Bez ohledu na to, jaký spouštěč používáte, nastavte IsNetworkRequested na úlohu na pozadí, abyste zajistili, že síť zůstane aktivní, zatímco úloha na pozadí běží. Tím se instruuje infrastruktura úloh na pozadí, aby síť zůstala aktivní, zatímco úloha probíhá, i když zařízení přešlo do režimu připojeného pohotovostního režimu. Pokud úloha na pozadí nepoužívá IsNetworkRequested, nebude mít přístup k síti, když je v režimu Connected Standby.