Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье представлен обзор использования фоновых задач и описано, как создать новую фоновую задачу в приложении WinUI 3. Сведения о переносе приложений UWP с фоновыми задачами в WinUI см. в стратегии миграции задач Windows App SDK Background.
Фоновые задачи — это компоненты приложения, которые выполняются в фоновом режиме без пользовательского интерфейса. Они могут выполнять такие действия, как скачивание файлов, синхронизация данных, отправка уведомлений или обновление плиток. Их можно активировать различными событиями, такими как время, системные изменения, действия пользователя или push-уведомления. Эти задачи могут выполняться, если соответствующий триггер возникает даже в том случае, если приложение не находится в состоянии выполнения.
Реализация фоновых задач отличается для приложений UWP и WinUI. Сведения о переносе приложений UWP с фоновыми задачами в WinUI см. в стратегии миграции задач Windows App SDK Background.
Планировщик задач помогает настольным приложениям реализовать те же функции, которые предоставляются BackgroundTaskBuilder в приложениях UWP. Более подробная информация о реализации с использованием TaskScheduler доступна здесь.
Регистрация фоновой задачи
Используйте класс BackgroundTaskBuilder, включенный в Windows App SDK, чтобы зарегистрировать фоновую задачу, использующую компонент COM полного доверия.
В следующем примере показано, как зарегистрировать фоновую задачу с помощью C++. В примере Windows App SDK github вы увидите этот код регистрации в MainWindow.Xaml.cpp
auto access = co_await BackgroundExecutionManager::RequestAccessAsync();
// Unregister all existing background task registrations
auto allRegistrations = BackgroundTaskRegistration::AllTasks();
for (const auto& taskPair : allRegistrations)
{
IBackgroundTaskRegistration task = taskPair.Value();
task.Unregister(true);
}
//Using the Windows App SDK API for BackgroundTaskBuilder
winrt::Microsoft::Windows::ApplicationModel::Background::BackgroundTaskBuilder builder;
builder.Name(L"TimeZoneChangeTask");
SystemTrigger trigger = SystemTrigger(SystemTriggerType::TimeZoneChange, false);
auto backgroundTrigger = trigger.as<IBackgroundTrigger>();
builder.SetTrigger(backgroundTrigger);
builder.AddCondition(SystemCondition(SystemConditionType::InternetAvailable));
builder.SetTaskEntryPointClsid(__uuidof(winrt::BackgroundTaskInProcCPP::BackgroundTask));
try
{
builder.Register();
}
catch (...)
{
// Indicate an error was encountered.
}
В следующем примере показано, как зарегистрировать фоновую задачу с помощью C#. В примере Windows App SDK github этот код регистрации можно увидеть в MainWindow.Xaml.cpp.
await BackgroundExecutionManager.RequestAccessAsync();
// Unregister all existing background task registrations
var allRegistrations = BackgroundTaskRegistration.AllTasks;
foreach (var taskPair in allRegistrations)
{
IBackgroundTaskRegistration task = taskPair.Value;
task.Unregister(true);
}
//Using the Windows App SDK API for BackgroundTaskBuilder
var builder = new Microsoft.Windows.ApplicationModel.Background.BackgroundTaskBuilder();
builder.Name = "TimeZoneChangeTask";
var trigger = new SystemTrigger(SystemTriggerType.TimeZoneChange, false);
var backgroundTrigger = trigger as IBackgroundTrigger;
builder.SetTrigger(backgroundTrigger);
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
builder.SetTaskEntryPointClsid(typeof(BackgroundTask).GUID);
builder.Register();
Обратите внимание, что вызов метода SetEntryPointClsid принимает в качестве аргумента GUID для определяемого приложением класса, реализующего IBackgroundTask. Этот интерфейс рассматривается в разделе "Реализация IBackgroundTask " далее в этой статье.
Рекомендации по регистрации фоновых задач
При регистрации фоновых задач используйте следующие рекомендации.
Вызовите BackgroundExecutionManager.RequestAccessAsync перед регистрацией фоновых задач.
Не регистрируйте фоновую задачу несколько раз. Убедитесь, что фоновая задача еще не зарегистрирована перед регистрацией или, как в примере Windows App SDK, отмените регистрацию всех фоновых задач, а затем повторно зарегистрируйте задачи. Используйте класс BackgroundTaskRegistration для запроса существующих фоновых задач.
Используйте свойство BackgroundTaskBuilder.Name , чтобы указать понятное имя фоновой задачи, чтобы упростить отладку и обслуживание.
Реализация IBackgroundTask
IBackgroundTask — это интерфейс, предоставляющий один метод Run, который выполняется при вызове фоновой задачи. Приложения, использующие фоновые задачи, должны включать класс, реализующий IBackgroundTask.
В следующем примере показано, как реализовать IBackgroundTask с помощью C++. В примере Windows App SDK github этот код регистрации можно увидеть в BackgroundTask.cpp.
void BackgroundTask::Run(_In_ IBackgroundTaskInstance taskInstance)
{
// Get deferral to indicate not to kill the background task process as soon as the Run method returns
m_deferral = taskInstance.GetDeferral();
m_progress = 0;
taskInstance.Canceled({ this, &BackgroundTask::OnCanceled });
// Calling a method on the Window to inform that the background task is executed
winrt::Microsoft::UI::Xaml::Window window = winrt::BackgroundTaskBuilder::implementation::App::Window();
m_mainWindow = window.as<winrt::BackgroundTaskBuilder::IMainWindow>();
Windows::Foundation::TimeSpan period{ std::chrono::seconds{2} };
m_periodicTimer = Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer([this, lifetime = get_strong()](Windows::System::Threading::ThreadPoolTimer timer)
{
if (!m_cancelRequested && m_progress < 100)
{
m_progress += 10;
}
else
{
m_periodicTimer.Cancel();
// Indicate that the background task has completed.
m_deferral.Complete();
if (m_cancelRequested) m_progress = -1;
}
m_mainWindow.BackgroundTaskExecuted(m_progress);
}, period);
}
void BackgroundTask::OnCanceled(_In_ IBackgroundTaskInstance /* taskInstance */, _In_ BackgroundTaskCancellationReason /* cancelReason */)
{
m_cancelRequested = true;
}
В следующем примере показано, как реализовать IBackgroundTask с помощью C#. В примере Windows App SDK github этот код регистрации можно увидеть в BackgroundTask.cpp.
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("00001111-aaaa-2222-bbbb-3333cccc4444")]
[ComSourceInterfaces(typeof(IBackgroundTask))]
public class BackgroundTask : IBackgroundTask
{
/// <summary>
/// This method is the main entry point for the background task. The system will believe this background task
/// is complete when this method returns.
/// </summary>
[MTAThread]
public void Run(IBackgroundTaskInstance taskInstance)
{
// Get deferral to indicate not to kill the background task process as soon as the Run method returns
_deferral = taskInstance.GetDeferral();
// Wire the cancellation handler.
taskInstance.Canceled += this.OnCanceled;
// Set the progress to indicate this task has started
taskInstance.Progress = 0;
_periodicTimer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler(PeriodicTimerCallback), TimeSpan.FromSeconds(1));
}
// Simulate the background task activity.
private void PeriodicTimerCallback(ThreadPoolTimer timer)
{
if ((_cancelRequested == false) && (_progress < 100))
{
_progress += 10;
}
else
{
if (_cancelRequested) _progress = -1;
if (_periodicTimer != null) _periodicTimer.Cancel();
// Indicate that the background task has completed.
if (_deferral != null) _deferral.Complete();
}
BackgroundTaskBuilder.MainWindow.taskStatus(_progress);
}
/// <summary>
/// This method is signaled when the system requests the background task be canceled. This method will signal
/// to the Run method to clean up and return.
/// </summary>
[MTAThread]
public void OnCanceled(IBackgroundTaskInstance taskInstance, BackgroundTaskCancellationReason cancellationReason)
{
// Handle cancellation operations and flag the task to end
_cancelRequested = true;
}
Рекомендации по реализации IBackgroundTask
При реализации IBackgroundTask используйте следующие рекомендации.
- Если фоновая задача будет выполнять асинхронные операции, получите объект отсрочки , вызвав GetDeferral для объекта ITaskInstance , переданного в Run. Это предотвращает преждевременное завершение хоста фоновых задач,
backgroundtaskhost.exe, до завершения операций. Освободите отсрочку после завершения выполнения всех асинхронных задач. - Максимально облегчайте задачи. Выполнение длительных задач может быть отменено системой, и их выполнение не рекомендуется.
- Используйте ведение журнала для сбора сведений о выполнении для устранения неполадок.
- Дополнительные рекомендации по реализации фоновых задач см. в рекомендациях по фоновым задачам.
Объявите расширение приложения для фоновой задачи в манифесте приложения.
Необходимо объявить расширение приложения в файле приложения Package.appxmanifest , чтобы зарегистрировать фоновую задачу в системе при установке приложения и предоставить сведения, необходимые системе для запуска фоновой задачи.
Необходимо добавить расширение с категорией windows.backgroundTasks в манифест приложения, чтобы фоновая задача была успешно зарегистрирована при установке приложения. Приложения C# должны указать значение атрибута EntryPoint "Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task". Для приложений C++ это добавляется автоматически, задав параметр WindowsAppSDKBackgroundTask значение true в файле project.
Необходимо также объявить com:Extension со значением категории "windows.comServer". Необходимо указать атрибут LaunchAndActivationPermission в элементе com:ExeServer , чтобы явно предоставить backgroundtaskhost.exe разрешение процесса для вызова класса COM. Сведения о формате этой строки см. в разделе "Формат строки дескриптора безопасности".
Убедитесь, что идентификатор класса, указанный в элементе com:Class , соответствует идентификатору класса для реализации IBackgroundTask.
В следующем примере показан синтаксис объявлений расширения приложения в файле манифеста приложения, чтобы система обнаружила и запустила фоновую задачу. Чтобы просмотреть полный файл манифеста приложения для фоновой задачи github, см. статью Package.appxmanifest.
<Extensions>
<Extension Category="windows.backgroundTasks" EntryPoint="Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task">
<BackgroundTasks>
<Task Type="general"/>
</BackgroundTasks>
</Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="BackgroundTaskBuilder.exe" DisplayName="BackgroundTask"
LaunchAndActivationPermission="O:PSG:BUD:(A;;11;;;IU)(A;;11;;;S-1-15-2-1)S:(ML;;NX;;;LW)">
<com:Class Id="00001111-aaaa-2222-bbbb-3333cccc4444" DisplayName="BackgroundTask" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
Регистрация COM-сервера для фоновой задачи
Регистрация COM-сервера гарантирует, что система знает, как создать экземпляр фонового класса задач при вызове backgroundtaskhost.exe. Необходимо зарегистрировать фабрику классов COM для фоновой задачи, вызвав CoRegisterClassObject, иначе активация COM завершится неудачей.
Регистрация COM-сервера в C++
В следующем примере показана вспомогательная функция C++ RegisterBackgroundTaskFactory, которая регистрирует фабрику класса для класса, реализующего IBackgroundTask. В этом примере этот класс называется BackgroundTask. В деструкторе вспомогательного класса вызывается CoRevokeClassObject для отмены регистрации класс-фабрики.
Этот вспомогательный класс отображается в примере репозитория в файле RegisterForCOM.cpp.
hresult RegisterForCom::RegisterBackgroundTaskFactory()
{
hresult hr;
try
{
com_ptr<IClassFactory> taskFactory = make<BackgroundTaskFactory>();
check_hresult(CoRegisterClassObject(__uuidof(BackgroundTask),
taskFactory.detach(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&ComRegistrationToken));
OutputDebugString(L"COM Registration done");
hr = S_OK;
}
CATCH_RETURN();
}
RegisterForCom::~RegisterForCom()
{
if (ComRegistrationToken != 0)
{
CoRevokeClassObject(ComRegistrationToken);
}
}
Для регистрации сервера в proc на C++вызовите вспомогательный класс, чтобы зарегистрировать фабрику классов из метода Application.OnLaunched . Просмотрите вызов вспомогательного метода в примере репозитория в App.xaml.cpp.
void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
window = make<MainWindow>();
window.Activate();
// Start COM server for the COM calls to complete
comRegister.RegisterBackgroundTaskFactory();
}
Для фоновых задач, выполняемых вне основного процесса, регистрация COM-сервера должна быть выполнена во время запуска. В App.xaml.cpp можно увидеть вызов вспомогательного класса.
int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR lpCmdLine, _In_ int)
{
if (std::wcsncmp(lpCmdLine, RegisterForCom::RegisterForComToken, sizeof(RegisterForCom::RegisterForComToken)) == 0)
{
winrt::init_apartment(winrt::apartment_type::multi_threaded);
RegisterForCom comRegister;
// Start COM server and wait for the COM calls to complete
comRegister.RegisterAndWait(__uuidof(BackgroundTask));
OutputDebugString(L"COM Server Shutting Down");
}
else
{
// put your fancy code somewhere here
::winrt::Microsoft::UI::Xaml::Application::Start(
[](auto&&)
{
::winrt::make<::winrt::BackgroundTaskBuilder::implementation::App>();
});
}
return 0;
}
Регистрация COM-сервера в C#
В следующем примере показана вспомогательная функция C# CreateInstance, которая регистрирует фабрику классов для класса, реализующего IBackgroundTask. В этом примере этот класс называется BackgroundTask. Вспомогательный класс использует LibraryImportAttribute для доступа к методам нативной регистрации COM из C#. Дополнительные сведения см. в разделе "Создание источника" для вызовов платформы. Реализацию вспомогательного класса можно увидеть в примере репозитория в ComServer.cs.
static partial class ComServer
{
[LibraryImport("ole32.dll")]
public static partial int CoRegisterClassObject(
ref Guid classId,
[MarshalAs(UnmanagedType.Interface)] IClassFactory objectAsUnknown,
uint executionContext,
uint flags,
out uint registrationToken);
[LibraryImport("ole32.dll")]
public static partial int CoRevokeObject(out uint registrationToken);
public const uint CLSCTX_LOCAL_SERVER = 4;
public const uint REGCLS_MULTIPLEUSE = 1;
public const uint S_OK = 0x00000000;
public const uint CLASS_E_NOAGGREGATION = 0x80040110;
public const uint E_NOINTERFACE = 0x80004002;
public const string IID_IUnknown = "00000000-0000-0000-C000-000000000046";
public const string IID_IClassFactory = "00000001-0000-0000-C000-000000000046";
[GeneratedComInterface]
[Guid(IID_IClassFactory)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IClassFactory
{
[PreserveSig]
uint CreateInstance(IntPtr objectAsUnknown, in Guid interfaceId, out IntPtr objectPointer);
[PreserveSig]
uint LockServer([MarshalAs(UnmanagedType.Bool)] bool Lock);
}
[GeneratedComClass]
internal sealed partial class BackgroundTaskFactory : IClassFactory
{
public uint CreateInstance(IntPtr objectAsUnknown, in Guid interfaceId, out IntPtr objectPointer)
{
if (objectAsUnknown != IntPtr.Zero)
{
objectPointer = IntPtr.Zero;
return CLASS_E_NOAGGREGATION;
}
if ((interfaceId != typeof(BackgroundTask).GUID) && (interfaceId != new Guid(IID_IUnknown)))
{
objectPointer = IntPtr.Zero;
return E_NOINTERFACE;
}
objectPointer = MarshalInterface<IBackgroundTask>.FromManaged(new BackgroundTask());
return S_OK;
}
public uint LockServer(bool lockServer) => S_OK;
}
}
Для фоновых задач в C#регистрация COM выполняется во время запуска приложения в конструкторе объекта Application . Вызов вспомогательного метода можно увидеть в примере репозитория в App.xaml.cs.
public App()
{
this.InitializeComponent();
Guid taskGuid = typeof(BackgroundTask).GUID;
ComServer.CoRegisterClassObject(ref taskGuid,
new ComServer.BackgroundTaskFactory(),
ComServer.CLSCTX_LOCAL_SERVER,
ComServer.REGCLS_MULTIPLEUSE,
out _RegistrationToken);
}
~App()
{
ComServer.CoRevokeObject(out _RegistrationToken);
}
Для внепроцессных задач в C# необходимо выполнить регистрацию COM при запуске приложения. Для этого необходимо отключить созданную по умолчанию точку входа XAML-генерации Main путем обновления файла проекта приложения.
В шаблоне проекта по умолчанию точка входа метода Main автоматически создается компилятором. Этот пример отключит автоматическую генерацию Main, чтобы при запуске можно было выполнить необходимый код активации.
- В Solution Explorer щелкните правой кнопкой мыши значок project и выберите "Изменить Project файл".
- В элементе PropertyGroup добавьте следующий дочерний элемент, чтобы отключить автоматически созданную главную функцию.
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
Вы можете увидеть вызов вспомогательного класса, чтобы зарегистрировать класс COM в примере репозитория в Program.cs
public class Program
{
static private uint _RegistrationToken;
static private ManualResetEvent _exitEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
if (args.Contains("-RegisterForBGTaskServer"))
{
Guid taskGuid = typeof(BackgroundTask).GUID;
ComServer.CoRegisterClassObject(ref taskGuid,
new ComServer.BackgroundTaskFactory(),
ComServer.CLSCTX_LOCAL_SERVER,
ComServer.REGCLS_MULTIPLEUSE,
out _RegistrationToken);
// Wait for the exit event to be signaled before exiting the program
_exitEvent.WaitOne();
}
else
{
App.Start(p => new App());
}
}
public static void SignalExit()
{
_exitEvent.Set();
}
}
Связанный контент
- Рекомендации по фоновым задачам в приложениях UWP
- стратегия миграции фоновых задач
Windows developer