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

Важные API

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

Примечание

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

Примечание

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

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

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

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

  1. Создайте новый проект для фоновых задач и добавьте его в решение. Для этого щелкните правой кнопкой мыши узел решения в Обозреватель решений и выберите AddNew> Project. Затем выберите тип проекта компонента среда выполнения 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. В точке входа: текстовое поле введите пространство имен и имя фонового класса, который используется в этом примере: Tasks.ExampleBackgroundTask.
  6. Закройте конструктор манифестов.

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

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

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

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

Примечание

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

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

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

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

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