Создание и регистрация внепроцессной фоновой задачи

Важные API

Создайте класс фоновой задачи и зарегистрируйте его выполнение, когда приложение не работает на переднем плане. В этом разделе рассказывается, как создать и зарегистрировать фоновую задачу, которая будет запускаться в отдельном процессе, а не в процессе вашего приложения. Руководство по реализации выполнения фоновой задачи непосредственно в приложении переднего плана см. в разделе Создание и регистрация фоновой задачи, выполняемой внутри процесса.

Примечание

Если фоновая задача используется для воспроизведения мультимедиа в фоновом режиме, см. раздел Воспроизведение мультимедиа в фоновом режиме, где приведены сведения об улучшениях в Windows 10 версии 1607, которые значительно упрощают работу.

Примечание

Если вы реализуете фоновую задачу вне процесса в классическом приложении C# с .NET 6 или более поздней версии, используйте поддержку разработки C#/WinRT для создания компонента среда выполнения Windows. Это относится к приложениям, использующим Windows App SDK, WinUI 3, WPF или WinForms. Пример см. в примере фоновой задачи .

Создание класса фоновой задачи

Для выполнения кода в фоновом режиме можно создавать классы, в которых реализован интерфейс IBackgroundTask. Этот код выполняется при активации определенного события с помощью, например SystemTrigger или MaintenanceTrigger.

Далее будет показано, как создать новый класс, реализующий интерфейс IBackgroundTask.

  1. Создайте новый проект для фоновых задач и добавьте его в решение. Для этого щелкните правой кнопкой мыши узел решения в Обозреватель решений и выберите Добавить>новый проект. Затем выберите тип проекта среда выполнения Windows Компонент, назовите проект и нажмите кнопку ОК.
  2. Создайте ссылку на проект фоновых задач в проекте приложения 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 . Это важный шаг: если приложение не проверяет, зарегистрирована ли уже фоновая задача, оно может выполнить регистрацию несколько раз, вызывая проблемы производительности и полное использование доступного задаче времени ЦП до завершения работы.

В следующем примере выполняется итерацию по свойству 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. Зарегистрируйте фоновую задачу, вызвав метод Register в объекте BackgroundTaskBuilder . Сохраните результат выполнения 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 для обработки завершения фоновых задач. Например, результат фоновой задачи может быть причиной обновления пользовательского интерфейса. Представленный здесь объем памяти метода необходим для метода обработчика событий OnCompleted, даже если в этом примере не используется параметр args.

Следующий пример кода распознает завершение фоновой задачи и вызывает пример метода (принимающего строку сообщения) для обновления пользовательского интерфейса.

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

Примечание

Обновления пользовательского интерфейса должны выполняться асинхронно, чтобы избежать остановки потока пользовательского интерфейса. Пример см. в методе 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. В текстовом поле Entry point: (Точка входа: ) введите пространство имен и имя фонового класса, который для этого примера — Tasks.ExampleBackgroundTask.
  6. Закройте конструктор манифестов.

Следующий элемент расширений добавляется в файл Package.appxmanifest для регистрации фоновой задачи:

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

Сводка и дальнейшие действия

Теперь вы понимаете, как создавать класс фоновой задачи, как регистрировать фоновую задачу из приложения и как сделать так, чтобы приложение распознавало ее завершение. Вы также знаете, как обновить манифест приложения, чтобы приложение могло успешно регистрировать фоновые задачи.

Примечание

Скачайте пример фоновой задачи, чтобы увидеть похожие примеры кода в контексте полного и надежного приложения UWP, использующего фоновые задачи.

В статьях ниже можно найти справочник по API, концептуальное руководство по фоновым задачам и подробные инструкции по созданию приложений, использующих фоновые задачи.

Учебные статьи с подробными сведениями о фоновых задачах

Руководство по фоновым задачам

Справочник по API для фоновых задач