Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

04 Jul 2020

UWP文档笔记:幕后任务【二】

Create and register an in-process background task

即便App还在前台,同进程的幕后任务也可能被操作系统终结,如果它们运行时间超限的话。

1607新增了两个跟前后台切换相关的事件EnteredBackground和LeavingBackground 。可以通过EnteredBackground来启动需要在后台执行的代码。

和异进程幕后任务一样,可以通过BackgroundTaskBuilder来注册同进程的幕后任务,一点区别是同进程情况下不需要指定TaskEntryPoint,因为默认的加载点是OnBackgroundActivated。同进程幕后任务也不需要在app manifest里面声明。

注册幕后任务前同样必须调用RequestAccessAsync

可以用变量来追踪同进程幕后任务的进度。

同进程幕后任务也可以被中结,处理中结的时候,BackgroundActivated的事件处理器必须先退出才行,否则整个进程会被终结。

Create and register an out-of-process background task

本文介绍如何创建并注册一个异进程的幕后任务,这样可以在App切换到幕后的时候运行。

此幕后任务需要实现IBackgroundTask接口,这样一来,此幕后任务可以通过SystemTrigger或者MaintenanceTrigger触发。IBackgroundTask.Run 将是此任务的入口点。

另外,此幕后任务需要在单独的一个Windows Runtime Component项目中。

C#的话,幕后任务工程中的类必须是公开的,而且必须是sealed或者final的。

C++/WinRT的一个例子:

// 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();
    }
}

如果在幕后任务中运行异步任务,那么异步任务必须采用Deferral来延迟退出。

通过遍历BackgroundTaskRegistration.AllTasks可以判断异步任务是否已经注册。否则可能导致异步任务被注册多次。

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.

对于已注册的任务,可以通过 BackgroundTaskBuilder来创建一个实例:

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.
}

builder还支持对trigger增加额外的触发条件:

builder.AddCondition(Windows::ApplicationModel::Background::SystemCondition{ Windows::ApplicationModel::Background::SystemConditionType::UserPresent });
// The code in the next step goes here.

通过调用builder的Register()方法来注册此任务

Windows::ApplicationModel::Background::BackgroundTaskRegistration task{ builder.Register() };

对于background trigger类型的幕后任务,在注册前必须先调用RequestAccessAsync 。

系统触发器ServicingComplete 可以用来执行App的升级后的自发更新,比如迁跃数据库还有更新幕后任务。最好先删除旧版本的幕后任务再添加新版本的。

为了获得幕后任务的处理结果,App需要注册回调到BackgroundTaskCompletedEventHandler。当App被加载或者恢复时,注册的回调会被调用,如果幕后任务完成了的话(如果你的App是在幕前,那么注册的回调会被直接执行)。

幕后任务必须在app manifest中声明。否则就会遇到"runtime class not registered” 错误。

幕后任务的声明在Package.appxmanifes中的模样:

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

将异进程幕后任务转为同进程的,最简单的做法是将IBackgroundTask.Run 中的代码迁移到OnBackgroundActivated中

多个幕后任务,可以通过BackgroundActivatedEventArgs.TaskInstance.Task.Name来区分。

同进程幕后任务与前台之间不需要额外的通信机制,因为在同一个STA中。

同进程幕后任务有一些限制:

  • 不能执行VoIP
  • 不支持DeviceUseTrigger, DeviceServicingTrigger 以及 IoTStartupTask。

(本篇完)

comments powered by Disqus