次の方法で共有


Windows アプリでのバックグラウンド タスクの使用

この記事では、バックグラウンド タスクの使用の概要と、WinUI 3 アプリで新しいバックグラウンド タスクを作成する方法について説明します。 バックグラウンド タスクを含む UWP アプリを WinUI に移行する方法については、Windows App SDK バックグラウンド タスク移行戦略を参照してください。

バックグラウンド タスクは、ユーザー インターフェイスなしでバックグラウンドで実行されるアプリ コンポーネントです。 ファイルのダウンロード、データの同期、通知の送信、タイルの更新などのアクションを実行できます。 これらは、時間、システム変更、ユーザー アクション、プッシュ通知など、さまざまなイベントによってトリガーできます。 これらのタスクは、アプリが実行状態でない場合でも、対応するトリガーが発生したときに実行されます。

バックグラウンド タスクの実装は、UWP アプリと WinUI アプリでは異なります。 バックグラウンド タスクを含む UWP アプリを WinUI に移行する方法については、Windows App SDK バックグラウンド タスク移行戦略を参照してください。

タスク スケジューラ は、デスクトップ アプリが UWP アプリで BackgroundTaskBuilder によって提供されるのと同じ機能を実現するのに役立ちます。 TaskScheduler を使用した実装の詳細については、こちらをご参照ください。

バックグラウンド タスクを登録する

Windows App SDK に含まれる BackgroundTaskBuilder クラスを使用して、完全信頼 COM コンポーネントを使用するバックグラウンド タスクを登録します。

次の例は、C++ を使用してバックグラウンド タスクを登録する方法を示しています。 Windows App SDK の github サンプルでは、この登録コードを MainWindow.Xaml.cpp で見ることができます。

auto access = co_await BackgroundExecutionManager::RequestAccessAsync();

// Unregister all existing background task registrations
auto allRegistrations = BackgroundTaskRegistration::AllTasks();
for (const auto& taskPair : allRegistrations)
{
    IBackgroundTaskRegistration task = taskPair.Value();
    task.Unregister(true);
}

//Using the Windows App SDK API for BackgroundTaskBuilder
winrt::Microsoft::Windows::ApplicationModel::Background::BackgroundTaskBuilder builder;
builder.Name(L"TimeZoneChangeTask");
SystemTrigger trigger = SystemTrigger(SystemTriggerType::TimeZoneChange, false);
auto backgroundTrigger = trigger.as<IBackgroundTrigger>();
builder.SetTrigger(backgroundTrigger);
builder.AddCondition(SystemCondition(SystemConditionType::InternetAvailable));
builder.SetTaskEntryPointClsid(__uuidof(winrt::BackgroundTaskInProcCPP::BackgroundTask));

try
{
    builder.Register();
}
catch (...)
{
    // Indicate an error was encountered.
}

次の例は、C# を使用してバックグラウンド タスクを登録する方法を示しています。 Windows App SDK github サンプルでは、この登録コードを MainWindow.Xaml.cppで確認できます。

await BackgroundExecutionManager.RequestAccessAsync();

// Unregister all existing background task registrations
var allRegistrations = BackgroundTaskRegistration.AllTasks;
foreach (var taskPair in allRegistrations)
{
    IBackgroundTaskRegistration task = taskPair.Value;
    task.Unregister(true);
}

//Using the Windows App SDK API for BackgroundTaskBuilder
var builder = new Microsoft.Windows.ApplicationModel.Background.BackgroundTaskBuilder();
builder.Name = "TimeZoneChangeTask";
var trigger = new SystemTrigger(SystemTriggerType.TimeZoneChange, false);
var backgroundTrigger = trigger as IBackgroundTrigger;
builder.SetTrigger(backgroundTrigger);
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
builder.SetTaskEntryPointClsid(typeof(BackgroundTask).GUID);
builder.Register();

SetEntryPointClsid メソッドの呼び出しは、IBackgroundTask を実装するアプリ定義クラスの GUID を引数として受け取ります。 このインターフェイスについては、この記事の後半の「 IBackgroundTask の実装 」セクションで説明します。

バックグラウンド タスク登録のベスト プラクティス

バックグラウンド タスクを登録する場合は、次のベスト プラクティスを使用します。

  • バックグラウンド タスクを登録する前に BackgroundExecutionManager.RequestAccessAsync を呼び出します。

  • バックグラウンド タスクを複数回登録しないでください。 登録する前にバックグラウンド タスクがまだ登録されていないことを確認するか、Windows App SDK サンプルのように、すべてのバックグラウンド タスクの登録を解除してから、タスクを再登録します。 BackgroundTaskRegistration クラスを使用して、既存のバックグラウンド タスクのクエリを実行します。

  • デバッグとメンテナンスを簡略化するために 、BackgroundTaskBuilder.Name プロパティを使用してバックグラウンド タスクのわかりやすい名前を指定します。

IBackgroundTask を実装する

IBackgroundTask は、バックグラウンド タスクが呼び出されたときに実行される 1 つのメソッド Run を公開するインターフェイスです。 バックグラウンド タスクを使用するアプリには、 IBackgroundTask を実装するクラスを含める必要があります。

次の例は、C++ を使用して IBackgroundTask を 実装する方法を示しています。 Windows App SDK github サンプルでは、この登録コードを BackgroundTask.cppで確認できます。

 void BackgroundTask::Run(_In_ IBackgroundTaskInstance taskInstance)
{
    // Get deferral to indicate not to kill the background task process as soon as the Run method returns
    m_deferral = taskInstance.GetDeferral();
    m_progress = 0;
    taskInstance.Canceled({ this, &BackgroundTask::OnCanceled });

    // Calling a method on the Window to inform that the background task is executed
    winrt::Microsoft::UI::Xaml::Window window = winrt::BackgroundTaskBuilder::implementation::App::Window();
    m_mainWindow = window.as<winrt::BackgroundTaskBuilder::IMainWindow>();

    Windows::Foundation::TimeSpan period{ std::chrono::seconds{2} };
    m_periodicTimer = Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer([this, lifetime = get_strong()](Windows::System::Threading::ThreadPoolTimer timer)
        {
            if (!m_cancelRequested && m_progress < 100)
            {
                m_progress += 10;
            }
            else
            {
                m_periodicTimer.Cancel();

                // Indicate that the background task has completed.
                m_deferral.Complete();
                if (m_cancelRequested) m_progress = -1;
            }
            m_mainWindow.BackgroundTaskExecuted(m_progress);
        }, period);
}

void BackgroundTask::OnCanceled(_In_ IBackgroundTaskInstance /* taskInstance */, _In_ BackgroundTaskCancellationReason /* cancelReason */)
{
    m_cancelRequested = true;
}

次の例は、C# を使用して IBackgroundTask を 実装する方法を示しています。 Windows App SDK github サンプルでは、この登録コードを BackgroundTask.cppで確認できます。

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("00001111-aaaa-2222-bbbb-3333cccc4444")]
[ComSourceInterfaces(typeof(IBackgroundTask))]
public class BackgroundTask : IBackgroundTask
{
    /// <summary>
    /// This method is the main entry point for the background task. The system will believe this background task
    /// is complete when this method returns.
    /// </summary>
    [MTAThread]
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        // Get deferral to indicate not to kill the background task process as soon as the Run method returns
        _deferral = taskInstance.GetDeferral();
        // Wire the cancellation handler.
        taskInstance.Canceled += this.OnCanceled;

        // Set the progress to indicate this task has started
        taskInstance.Progress = 0;

        _periodicTimer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler(PeriodicTimerCallback), TimeSpan.FromSeconds(1));
    }

    // Simulate the background task activity.
    private void PeriodicTimerCallback(ThreadPoolTimer timer)
    {
        if ((_cancelRequested == false) && (_progress < 100))
        {
            _progress += 10;
        }
        else
        {
            if (_cancelRequested) _progress = -1;
            if (_periodicTimer != null) _periodicTimer.Cancel();

            // Indicate that the background task has completed.
            if (_deferral != null) _deferral.Complete();
        }

        BackgroundTaskBuilder.MainWindow.taskStatus(_progress);
    }

    /// <summary>
    /// This method is signaled when the system requests the background task be canceled. This method will signal
    /// to the Run method to clean up and return.
    /// </summary>
    [MTAThread]
    public void OnCanceled(IBackgroundTaskInstance taskInstance, BackgroundTaskCancellationReason cancellationReason)
    {
        // Handle cancellation operations and flag the task to end
        _cancelRequested = true;
    }

IBackgroundTask を実装するためのベスト プラクティス

IBackgroundTask を実装する場合は、次のベスト プラクティスを使用します。

  • バックグラウンド タスクが非同期操作を実行する場合は、Run に渡された ITaskInstance オブジェクトで GetDeferral を呼び出し、延期オブジェクトを取得します。 これにより、バックグラウンド タスク ホスト ( backgroundtaskhost.exe) は、操作が完了する前に途中で終了できなくなります。 すべての非同期タスクが完了したら、遅延を解放します。
  • タスクはできるだけ軽量にしてください。 実行時間の長いタスクは、システムによって終了される可能性があるため、推奨されません。
  • ログ記録を使用して、トラブルシューティングのために実行の詳細をキャプチャします。
  • バックグラウンド タスクを実装するためのその他のベスト プラクティスについては、「 バックグラウンド タスクのガイドライン」を参照してください。

アプリ マニフェストでバックグラウンド タスク アプリ拡張機能を宣言する

アプリのインストール時にバックグラウンド タスクをシステムに登録し、バックグラウンド タスクを起動するためにシステムに必要な情報を提供するには、アプリの Package.appxmanifest ファイルでアプリ拡張機能を宣言する必要があります。

アプリのインストール時にバックグラウンド タスクを正常に登録するには、 windows.backgroundTasks というカテゴリの拡張機能をアプリ マニフェストに追加する必要があります。 C# アプリでは 、EntryPoint 属性値 "Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task" を指定する必要があります。 C++ アプリの場合、プロジェクト ファイルで WindowsAppSDKBackgroundTasktrue に設定すると、これが自動的に追加されます。

また、"windows.comServer" というカテゴリ値を使用して com:Extension を宣言する必要があります。 com:ExeServer 要素で LaunchAndActivationPermission 属性を指定して、backgroundtaskhost.exe プロセスに COM クラスを呼び出すアクセス許可を明示的に付与する必要があります。 この文字列の形式については、「 セキュリティ記述子の文字列形式」を参照してください。

com:Class 要素で指定するクラス ID が、IBackgroundTask の実装のクラス ID と一致していることを確認します。

次の例は、システムがバックグラウンド タスクを検出して起動できるようにするための、アプリ マニフェスト ファイル内のアプリケーション拡張機能宣言の構文を示しています。 github でバックグラウンド タスクの完全なアプリ マニフェスト ファイルを表示するには、 Package.appxmanifest を参照してください。

<Extensions>
    <Extension Category="windows.backgroundTasks" EntryPoint="Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task">
        <BackgroundTasks>
            <Task Type="general"/>
        </BackgroundTasks>
    </Extension>
    <com:Extension Category="windows.comServer">
        <com:ComServer>
            <com:ExeServer Executable="BackgroundTaskBuilder.exe" DisplayName="BackgroundTask"
                                LaunchAndActivationPermission="O:PSG:BUD:(A;;11;;;IU)(A;;11;;;S-1-15-2-1)S:(ML;;NX;;;LW)">
                <com:Class Id="00001111-aaaa-2222-bbbb-3333cccc4444" DisplayName="BackgroundTask" />
            </com:ExeServer>
        </com:ComServer>
    </com:Extension>
</Extensions>

バックグラウンド タスクの COM サーバーを登録する

COM サーバーを登録すると、 CoCreateInstancebackgroundtaskhost.exe によって呼び出されたときに、バックグラウンド タスク クラスをインスタンス化する方法がシステムで認識されるようになります。 CoRegisterClassObject を呼び出すことによって、バックグラウンド タスクの COM クラス ファクトリを登録する必要があります。または、COM のアクティブ化が失敗します。

C++ での COM サーバーの登録

次の例は、 IBackgroundTask を実装するクラスのクラス ファクトリを登録する C++ ヘルパー関数 RegisterBackgroundTaskFactory を示しています。 この例では、このクラスは BackgroundTask と呼ばれます。 ヘルパー クラスのデストラクターでは、クラス ファクトリの登録を取り消すために CoRevokeClassObject が呼び出されます。

このヘルパー クラスは、ファイル RegisterForCOM.cppのサンプル リポジトリで確認できます。

hresult RegisterForCom::RegisterBackgroundTaskFactory()
{
    hresult hr;
    try
    {
        com_ptr<IClassFactory> taskFactory = make<BackgroundTaskFactory>();

        check_hresult(CoRegisterClassObject(__uuidof(BackgroundTask),
            taskFactory.detach(),
            CLSCTX_LOCAL_SERVER,
            REGCLS_MULTIPLEUSE,
            &ComRegistrationToken));

        OutputDebugString(L"COM Registration done");
        hr = S_OK;
    }
    CATCH_RETURN();
}

 RegisterForCom::~RegisterForCom()
{
    if (ComRegistrationToken != 0)
    {
        CoRevokeClassObject(ComRegistrationToken);
    }
}

C++ でのインプロセス サーバー登録の場合は、ヘルパー クラスを呼び出して 、Application.OnLaunched メソッド内からクラス ファクトリを登録します。 App.xaml.cppのサンプル リポジトリのヘルパー メソッドの呼び出しを参照してください。

void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
    window = make<MainWindow>();
    window.Activate();
    // Start COM server for the COM calls to complete
    comRegister.RegisterBackgroundTaskFactory();
}

アウトプロセス バックグラウンド タスクの場合は、スタートアップ プロセス中に COM サーバーの登録を行う必要があります。 ヘルパー クラスの呼び出しは 、App.xaml.cppで確認できます。

int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR lpCmdLine, _In_ int)
{
    if (std::wcsncmp(lpCmdLine, RegisterForCom::RegisterForComToken, sizeof(RegisterForCom::RegisterForComToken)) == 0)
    {
        winrt::init_apartment(winrt::apartment_type::multi_threaded);
        RegisterForCom comRegister;
        // Start COM server and wait for the COM calls to complete
        comRegister.RegisterAndWait(__uuidof(BackgroundTask));
        OutputDebugString(L"COM Server Shutting Down");
    }
    else
    {
        // put your fancy code somewhere here
        ::winrt::Microsoft::UI::Xaml::Application::Start(
            [](auto&&)
            {
                ::winrt::make<::winrt::BackgroundTaskBuilder::implementation::App>();
            });
    }

    return 0;
}

C での COM サーバーの登録#

次の例は、IBackgroundTask を実装するクラスのクラス ファクトリを登録する C# ヘルパー関数 CreateInstance を示しています。 この例では、このクラスは BackgroundTask と呼ばれます。 ヘルパー クラスは LibraryImportAttribute を使用して、C# からネイティブ COM 登録メソッドにアクセスします。 詳細については、「 プラットフォーム呼び出しのソース生成」を参照してください。 ComServer.csのサンプル リポジトリでヘルパー クラスの実装 確認できます。

static partial class ComServer
{
    [LibraryImport("ole32.dll")]
    public static partial int CoRegisterClassObject(
        ref Guid classId,
        [MarshalAs(UnmanagedType.Interface)] IClassFactory objectAsUnknown,
        uint executionContext,
        uint flags,
        out uint registrationToken);

    [LibraryImport("ole32.dll")]
    public static partial int CoRevokeObject(out uint registrationToken);

    public const uint CLSCTX_LOCAL_SERVER = 4;
    public const uint REGCLS_MULTIPLEUSE = 1;

    public const uint S_OK = 0x00000000;
    public const uint CLASS_E_NOAGGREGATION = 0x80040110;
    public const uint E_NOINTERFACE = 0x80004002;

    public const string IID_IUnknown = "00000000-0000-0000-C000-000000000046";
    public const string IID_IClassFactory = "00000001-0000-0000-C000-000000000046";

    [GeneratedComInterface]
    [Guid(IID_IClassFactory)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public partial interface IClassFactory
    {
        [PreserveSig]
        uint CreateInstance(IntPtr objectAsUnknown, in Guid interfaceId, out IntPtr objectPointer);

        [PreserveSig]
        uint LockServer([MarshalAs(UnmanagedType.Bool)] bool Lock);
    }

    [GeneratedComClass]
    internal sealed partial class BackgroundTaskFactory : IClassFactory
    {
        public uint CreateInstance(IntPtr objectAsUnknown, in Guid interfaceId, out IntPtr objectPointer)
        {
            if (objectAsUnknown != IntPtr.Zero)
            {
                objectPointer = IntPtr.Zero;
                return CLASS_E_NOAGGREGATION;
            }

            if ((interfaceId != typeof(BackgroundTask).GUID) && (interfaceId != new Guid(IID_IUnknown)))
            {
                objectPointer = IntPtr.Zero;
                return E_NOINTERFACE;
            }

            objectPointer = MarshalInterface<IBackgroundTask>.FromManaged(new BackgroundTask());
            return S_OK;
        }

        public uint LockServer(bool lockServer) => S_OK;
    }
}

C# のインプロセス バックグラウンド タスクの場合、COM 登録はアプリの起動時に Application オブジェクトのコンストラクターで実行されます。 App.xaml.csのサンプル リポジトリでヘルパー メソッドの呼び出しを確認できます。

public App()
{
    this.InitializeComponent();
    Guid taskGuid = typeof(BackgroundTask).GUID;
    ComServer.CoRegisterClassObject(ref taskGuid,
                                    new ComServer.BackgroundTaskFactory(),
                                    ComServer.CLSCTX_LOCAL_SERVER,
                                    ComServer.REGCLS_MULTIPLEUSE,
                                    out _RegistrationToken);
}

~App()
{
    ComServer.CoRevokeObject(out _RegistrationToken);
}

C# のアウトプロセス タスクの場合は、アプリの起動時に COM 登録を実行する必要があります。 これを行うには、アプリのプロジェクト ファイルを更新して、XAML で生成された 既定のメイン エントリ ポイントを無効にする必要があります。

既定のプロジェクト テンプレートでは、 Main メソッドのエントリ ポイントはコンパイラによって自動生成されます。 この例では、Main の自動生成を無効にして、起動時に必要なアクティブ化コードを実行できるようにします。

  1. ソリューション エクスプローラーで、プロジェクト アイコンを右クリックし、[プロジェクト ファイルの編集] を選択します。
  2. PropertyGroup 要素に次の子要素を追加して、自動生成された main 関数を無効にします。
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

ヘルパー クラスの呼び出しを確認して、com クラスをサンプル リポジトリに登録 Program.cs

public class Program
{
    static private uint _RegistrationToken;
    static private ManualResetEvent _exitEvent = new ManualResetEvent(false);

    static void Main(string[] args)
    {
        if (args.Contains("-RegisterForBGTaskServer"))
        {
            Guid taskGuid = typeof(BackgroundTask).GUID;
            ComServer.CoRegisterClassObject(ref taskGuid,
                                            new ComServer.BackgroundTaskFactory(),
                                            ComServer.CLSCTX_LOCAL_SERVER,
                                            ComServer.REGCLS_MULTIPLEUSE,
                                            out _RegistrationToken);

            // Wait for the exit event to be signaled before exiting the program
            _exitEvent.WaitOne();
        }
        else
        {
            App.Start(p => new App());
        }
    }

    public static void SignalExit()
    {
        _exitEvent.Set();
    }
}