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

重要的 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 参考