語言

在 Windows 應用程式中處理背景任務

備註

本文介紹使用 Windows 執行階段(WinRT)BackgroundTaskBuilder API 在 Windows 中建立的背景任務。ApplicationModel.Background 命名空間,適用於具有套件身份的應用程式,包括 UWP 及已打包的桌面應用程式。 如果你正在建置新應用程式或將現有應用程式遷移到 Windows 應用程式 SDK,請參考 在 Windows 應用程式中使用背景任務 以及 背景任務遷移策略。

學習如何在你的應用程式中建立並註冊背景任務,使用Windows 執行階段(WinRT)BackgroundTaskBuilder類別。

註冊背景任務

請參閱 BackgroundTask 範例,完整範例是如何在 通用 Windows 平台 (UWP) 應用程式中註冊背景任務。

以下範例展示了一個 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:背景任務入口點的類別 ID。
  • taskName背景任務名稱。

該 RegisterBackgroundTaskWithSystem 方法會建立一個新的類別實例 BackgroundTaskBuilder ,並為背景任務設定觸發點與入口類別 ID。 該方法接著向系統註冊背景任務。

備註

這個類別不是敏捷的,所以你需要考慮它的執行緒模型和編組行為。 更多資訊請參閱 執行緒與編組(C++/CX) 以及 在多執行緒環境中使用 Windows 執行階段 物件(.NET)。

在背景任務中處理現代待命

BackgroundTaskBuilder 及相關 API 已經允許打包桌面應用程式執行背景任務。 API 現在擴充這些 API,使這些應用程式能在現代待機狀態下執行程式碼。 更新同時新增了應用程式可查詢的屬性,以判斷系統是否會在現代待機時限制背景任務,以節省電池續航。 這使應用程式能夠在現代待命模式下接收到來自 VoIP 通話或其他推播通知的情境。

備註

本節中的「封裝桌面應用程式」指的是具有封裝識別碼(即桌面橋接或稀疏簽章封裝應用程式)且以主要(或主要)功能作為入口的 Win32 應用程式。

以下範例展示了應用程式開發者如何使用 BackgroundTaskBuilder API 來註冊最多一個指定任務名稱的任務。 範例也展示了如何檢查並選擇任務註冊,以現代待機模式執行應用程式最關鍵的任務。

// 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,那麼在 Connected 待機模式下,背景任務將無法存取網路。