다음을 통해 공유


Windows 앱에서 백그라운드 작업 관리하기

WinRT(Windows 런타임) BackgroundTaskBuilder 클래스를 사용하여 앱에서 백그라운드 작업을 만들고 등록하는 방법을 알아봅니다.

백그라운드 작업 등록

UWP(유니버설 Windows 플랫폼) 앱에서 백그라운드 작업을 등록하는 전체 예제는 BackgroundTask 샘플 참조하세요.

다음 예제에서는 되풀이 15분 타이머에서 실행되는 Win32 COM 작업의 등록을 보여 주세요.

백그라운드 작업을 등록하려면 먼저 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) 및 다중 스레드 환경(.NET) Windows 런타임 개체 사용참조하세요.

백그라운드 작업에서 모던 스탠바이를 처리하기

BackgroundTaskBuilder 및 관련 API는 이미 패키지된 데스크톱 애플리케이션에서 백그라운드 작업을 실행할 수 있도록 허용합니다. 이제 API는 이러한 API를 확장하여 이러한 appls가 최신 대기 상태로 코드를 실행할 수 있도록 합니다. 또한 이 업데이트는 앱에서 쿼리할 수 있는 속성을 추가하여 시스템이 최신 대기 상태의 애플리케이션에 대한 백그라운드 작업을 제한하여 배터리 수명을 절약할 수 있는지 확인합니다. 이를 통해 최신 대기에서 VoIP 호출 또는 기타 푸시 알림을 수신하는 앱과 같은 시나리오를 사용할 수 있습니다.

메모

이 섹션의 "패키지된 데스크톱 애플리케이션"은 패키지 ID(즉, 데스크톱 브리지 또는 스파스 서명된 패키지된 애플리케이션)가 있고 기본(또는 wmain) 함수를 진입점으로 사용하는 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.WasApplicationThrottledInStandbyBackgroundWorkCost.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 사용하지 않는 경우 연결된 대기 모드에서는 백그라운드 작업이 네트워크에 액세스할 수 없습니다.