创建和注册进程外后台任务

重要的 API

创建一个后台任务类并注册它,以便在应用不在前台运行时运行。 本主题演示了如何创建和注册在单独进程(而不是应用的进程)中运行的后台任务。 若要直接在前台应用程序中执行后台任务,请参阅创建和注册进程内后台任务

注意

如果你使用后台任务在后台播放媒体,请参阅在后台播放媒体,了解有关 Windows 10 版本 1607 中使此操作更加简单的改进信息。

注意

如果要在具有 .NET 6 或更高版本的 C# 桌面应用程序中实现进程外后台任务,请使用 C#/WinRT 创作支持来创建Windows 运行时组件。 这适用于使用 Windows 应用 SDK、WinUI 3、WPF 或 WinForms 的应用。 有关示例,请参阅 后台任务示例

创建后台任务类

你可以通过编写用于实现 IBackgroundTask 接口的类来在后台运行代码。 该代码在使用 SystemTriggerMaintenanceTrigger 等触发器触发特定事件时运行。

以下示例向你展示如何编写用于实现 IBackgroundTask 接口的新类。

  1. 为后台任务创建新项目并将其添加到你的解决方案。 若要执行此操作,请在“解决方案资源管理器”中右键单击你的解决方案节点,并选择“添加”>“新建项目”。 然后选择“Windows 运行时组件”项目类型,为该项目命名,然后单击“确定”。
  2. 从通用 Windows 平台 (UWP) 应用项目中引用后台任务项目。 对于你应用项目中的 C# 或 C++ 应用,请右键单击“引用”并选择“添加新引用”。 在解决方案下,选择项目,然后选择你的后台任务项目名称并单击确定
  3. 对于后台任务项目,请添加一个用于实现 IBackgroundTask 接口的新类。 IBackgroundTask.Run 方法是一个需要的入口点,当触发指定事件时,将调用该入口点;每个后台任务都需要用到该方法。

注意

后台任务类本身和后台任务项目中的所有其他类都需要是处于“封装”(或“最终”)状态的“公共”类。

以下示例代码显示一个用于后台任务类的非常基本的起点。

// ExampleBackgroundTask.cs
using Windows.ApplicationModel.Background;

namespace Tasks
{
    public sealed class ExampleBackgroundTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            
        }        
    }
}
// First, add ExampleBackgroundTask.idl, and then build.
// ExampleBackgroundTask.idl
namespace Tasks
{
    [default_interface]
    runtimeclass ExampleBackgroundTask : Windows.ApplicationModel.Background.IBackgroundTask
    {
        ExampleBackgroundTask();
    }
}

// ExampleBackgroundTask.h
#pragma once

#include "ExampleBackgroundTask.g.h"

namespace winrt::Tasks::implementation
{
    struct ExampleBackgroundTask : ExampleBackgroundTaskT<ExampleBackgroundTask>
    {
        ExampleBackgroundTask() = default;

        void Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance);
    };
}

namespace winrt::Tasks::factory_implementation
{
    struct ExampleBackgroundTask : ExampleBackgroundTaskT<ExampleBackgroundTask, implementation::ExampleBackgroundTask>
    {
    };
}

// ExampleBackgroundTask.cpp
#include "pch.h"
#include "ExampleBackgroundTask.h"

namespace winrt::Tasks::implementation
{
    void ExampleBackgroundTask::Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance)
    {
        throw hresult_not_implemented();
    }
}
// ExampleBackgroundTask.h
#pragma once

using namespace Windows::ApplicationModel::Background;

namespace Tasks
{
    public ref class ExampleBackgroundTask sealed : public IBackgroundTask
    {

    public:
        ExampleBackgroundTask();

        virtual void Run(IBackgroundTaskInstance^ taskInstance);
        void OnCompleted(
            BackgroundTaskRegistration^ task,
            BackgroundTaskCompletedEventArgs^ args
        );
    };
}

// ExampleBackgroundTask.cpp
#include "ExampleBackgroundTask.h"

using namespace Tasks;

void ExampleBackgroundTask::Run(IBackgroundTaskInstance^ taskInstance)
{
}
  1. 如果你在后台任务中运行任何异步代码,则你的后台任务需要使用延迟。 在不使用延迟的情况下,如果 Run 方法在任何异步工作运行完成之前返回,则后台任务进程可能会意外终止。

调用异步方法之前,在 Run 方法中请求延迟。 保存对类数据成员的延迟,以便可通过异步方法对其进行访问。 完成异步代码之后声明延迟完成。

以下示例代码获取并保存延迟,并在异步代码完成后将其释放。

BackgroundTaskDeferral _deferral; // Note: defined at class scope so that we can mark it complete inside the OnCancel() callback if we choose to support cancellation
public async void Run(IBackgroundTaskInstance taskInstance)
{
    _deferral = taskInstance.GetDeferral();
    //
    // TODO: Insert code to start one or more asynchronous methods using the
    //       await keyword, for example:
    //
    // await ExampleMethodAsync();
    //

    _deferral.Complete();
}
// ExampleBackgroundTask.h
...
private:
    Windows::ApplicationModel::Background::BackgroundTaskDeferral m_deferral{ nullptr };

// ExampleBackgroundTask.cpp
...
Windows::Foundation::IAsyncAction ExampleBackgroundTask::Run(
    Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance)
{
    m_deferral = taskInstance.GetDeferral(); // Note: defined at class scope so that we can mark it complete inside the OnCancel() callback if we choose to support cancellation.
    // TODO: Modify the following line of code to call a real async function.
    co_await ExampleCoroutineAsync(); // Run returns at this point, and resumes when ExampleCoroutineAsync completes.
    m_deferral.Complete();
}
void ExampleBackgroundTask::Run(IBackgroundTaskInstance^ taskInstance)
{
    m_deferral = taskInstance->GetDeferral(); // Note: defined at class scope so that we can mark it complete inside the OnCancel() callback if we choose to support cancellation.

    //
    // TODO: Modify the following line of code to call a real async function.
    //       Note that the task<void> return type applies only to async
    //       actions. If you need to call an async operation instead, replace
    //       task<void> with the correct return type.
    //
    task<void> myTask(ExampleFunctionAsync());

    myTask.then([=]() {
        m_deferral->Complete();
    });
}

注意

在 C# 中,可以使用 async/await 关键字调用后台任务的异步方法。 在 C++/CX 中,可通过使用任务链获得相似结果。

有关异步模式的详细信息,请参阅异步编程。 有关如何使用延迟阻止后台任务提前停止的其他示例,请参阅后台任务示例

以下步骤在你的一个应用类(例如 MainPage.xaml.cs)中完成。

注意

还可以创建专用于注册后台任务的函数,为此请参阅注册后台任务。 在此情况下,无需使用接下来的三个步骤,只需简单构造触发器,并将其随任务名称、任务入口点以及(可选)条件一起提供给注册函数。

注册要运行的后台任务

  1. 通过在 BackgroundTaskRegistration.AllTasks 属性中迭代,查明后台任务是否已注册。 此步骤非常重要;如果应用不检查现有后台任务注册,则它可能会轻松多次注册该任务,这会导致性能问题和工作结束前超出任务的最大可用 CPU 时间。

下例将在 AllTasks 属性上进行迭代,并且如果任务已经注册,则将标志参数设置为“true”。

var taskRegistered = false;
var exampleTaskName = "ExampleBackgroundTask";

foreach (var task in BackgroundTaskRegistration.AllTasks)
{
    if (task.Value.Name == exampleTaskName)
    {
        taskRegistered = true;
        break;
    }
}
std::wstring exampleTaskName{ L"ExampleBackgroundTask" };

auto allTasks{ Windows::ApplicationModel::Background::BackgroundTaskRegistration::AllTasks() };

bool taskRegistered{ false };
for (auto const& task : allTasks)
{
    if (task.Value().Name() == exampleTaskName)
    {
        taskRegistered = true;
        break;
    }
}

// The code in the next step goes here.
boolean taskRegistered = false;
Platform::String^ exampleTaskName = "ExampleBackgroundTask";

auto iter = BackgroundTaskRegistration::AllTasks->First();
auto hascur = iter->HasCurrent;

while (hascur)
{
    auto cur = iter->Current->Value;

    if(cur->Name == exampleTaskName)
    {
        taskRegistered = true;
        break;
    }

    hascur = iter->MoveNext();
}
  1. 如果后台任务尚未注册,则使用 BackgroundTaskBuilder 创建你的后台任务的一个实例。 任务入口点应为命名空间为前缀的后台任务的名称。

后台任务触发器控制后台任务何时运行。 有关可能的触发器的列表,请参阅 SystemTrigger

例如,此代码创建一个新后台任务并将其设置为在 TimeZoneChanged 触发器引发时运行:

var builder = new BackgroundTaskBuilder();

builder.Name = exampleTaskName;
builder.TaskEntryPoint = "Tasks.ExampleBackgroundTask";
builder.SetTrigger(new SystemTrigger(SystemTriggerType.TimeZoneChange, false));
if (!taskRegistered)
{
    Windows::ApplicationModel::Background::BackgroundTaskBuilder builder;
    builder.Name(exampleTaskName);
    builder.TaskEntryPoint(L"Tasks.ExampleBackgroundTask");
    builder.SetTrigger(Windows::ApplicationModel::Background::SystemTrigger{
        Windows::ApplicationModel::Background::SystemTriggerType::TimeZoneChange, false });
    // The code in the next step goes here.
}
auto builder = ref new BackgroundTaskBuilder();

builder->Name = exampleTaskName;
builder->TaskEntryPoint = "Tasks.ExampleBackgroundTask";
builder->SetTrigger(ref new SystemTrigger(SystemTriggerType::TimeZoneChange, false));
  1. (可选)在触发器事件发生后,你可以添加条件控制任务何时运行。 例如,如果你不希望在用户存在前运行任务,请使用条件 UserPresent。 有关可能条件的列表,请参阅 SystemConditionType

以下示例代码分配需要用户存在的条件:

builder.AddCondition(new SystemCondition(SystemConditionType.UserPresent));
builder.AddCondition(Windows::ApplicationModel::Background::SystemCondition{ Windows::ApplicationModel::Background::SystemConditionType::UserPresent });
// The code in the next step goes here.
builder->AddCondition(ref new SystemCondition(SystemConditionType::UserPresent));
  1. 通过在 BackgroundTaskBuilder 对象上调用 Register 方法来注册后台任务。 存储 BackgroundTaskRegistration 结果,以便可以在下一步中使用该结果。

以下代码注册后台任务并存储结果:

BackgroundTaskRegistration task = builder.Register();
Windows::ApplicationModel::Background::BackgroundTaskRegistration task{ builder.Register() };
BackgroundTaskRegistration^ task = builder->Register();

注意

通用 Windows 应用必须在注册任何后台触发器类型之前调用 RequestAccessAsync

若要确保通用 Windows 应用在你发布更新后继续正常运行,请使用 ServicingComplete(请参阅 SystemTriggerType)触发器执行任何更新后配置更改(如迁移应用的数据库和注册后台任务)。 最佳做法是注销与以前版本的应用关联的后台任务(请参阅 RemoveAccess),同时为新版本的应用注册后台任务(请参阅 RequestAccessAsync)。

有关详细信息,请参阅后台任务指南

使用事件处理程序处理后台任务完成

你应该使用 BackgroundTaskCompletedEventHandler 注册一个方法,以便应用可以从后台任务中获取结果。 当启动或恢复应用时,如果自从上次应用在前台运行后,后台任务就已完成,则将调用标记方法。 (如果应用当前位于前台时后台任务完成,将立即调用 OnCompleted 方法。)

  1. 编写一个 OnCompleted 方法,以处理后台任务的完成。 例如,后台任务结果可能导致 UI 更新。 此处所示的方法足迹对于 OnCompleted 事件处理程序方法来说是必需的,即使该示例不使用 args 参数也是如此。

以下示例代码识别后台任务完成并调用可获取消息字符串的一个示例 UI 更新方法。

private void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
    var settings = Windows.Storage.ApplicationData.Current.LocalSettings;
    var key = task.TaskId.ToString();
    var message = settings.Values[key].ToString();
    UpdateUI(message);
}
void UpdateUI(winrt::hstring const& message)
{
    MyTextBlock().Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [=]()
    {
        MyTextBlock().Text(message);
    });
}

void OnCompleted(
    Windows::ApplicationModel::Background::BackgroundTaskRegistration const& sender,
    Windows::ApplicationModel::Background::BackgroundTaskCompletedEventArgs const& /* args */)
{
	// You'll previously have inserted this key into local settings.
    auto settings{ Windows::Storage::ApplicationData::Current().LocalSettings().Values() };
    auto key{ winrt::to_hstring(sender.TaskId()) };
    auto message{ winrt::unbox_value<winrt::hstring>(settings.Lookup(key)) };

    UpdateUI(message);
}
void MainPage::OnCompleted(BackgroundTaskRegistration^ task, BackgroundTaskCompletedEventArgs^ args)
{
    auto settings = ApplicationData::Current->LocalSettings->Values;
    auto key = task->TaskId.ToString();
    auto message = dynamic_cast<String^>(settings->Lookup(key));
    UpdateUI(message);
}

注意

UI 更新应该异步执行,为的是避免占用 UI 线程。 有关示例,请参阅后台任务示例中的 UpdateUI 方法。

  1. 回到已注册后台任务的位置。 在该代码行之后,添加一个新的 BackgroundTaskCompletedEventHandler 对象。 提供 OnCompleted 方法作为 BackgroundTaskCompletedEventHandler 构造函数的参数。

以下示例代码将一个 BackgroundTaskCompletedEventHandler 添加到 BackgroundTaskRegistration

task.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
task.Completed({ this, &MainPage::OnCompleted });
task->Completed += ref new BackgroundTaskCompletedEventHandler(this, &MainPage::OnCompleted);

在应用清单中声明你的应用使用后台任务

必须先在应用清单中声明各个后台任务,你的应用才能运行后台任务。 如果你的应用尝试使用未在清单中列出的触发器来注册后台任务,则后台任务的注册将失败,并显示“运行时类未注册”错误。

  1. 通过打开名为 Package.appxmanifest 的文件打开程序包清单设计器。
  2. 打开“声明”选项卡。
  3. 可用声明下拉菜单中,选择后台任务,然后单击添加
  4. 选中系统事件复选框。
  5. 在“入口点:”文本框中,输入后台类的命名空间和名称,在此示例中为 Tasks.ExampleBackgroundTask。
  6. 关闭清单设计器。

以下 Extensions 元素将添加到 Package.appxmanifest 文件以注册后台任务:

<Extensions>
  <Extension Category="windows.backgroundTasks" EntryPoint="Tasks.ExampleBackgroundTask">
    <BackgroundTasks>
      <Task Type="systemEvent" />
    </BackgroundTasks>
  </Extension>
</Extensions>

总结和后续步骤

现在,你应该已基本了解如何编写后台任务类、如何从应用中注册后台任务,以及如何让应用识别后台任务何时完成。 你还应该了解如何更新应用程序清单,以便你的应用可以成功注册后台任务。

注意

下载后台任务示例以查看使用后台任务的完整且可靠的 UWP 应用上下文中的类似代码示例。

有关 API 引用、后台任务概念指南以及编写使用后台任务的应用的更多详细说明,请参阅以下相关主题。

详细说明后台任务主题

后台任务指南

后台任务 API 引用