Freigeben über


Verwenden von Hintergrundaufgaben in Windows-Apps

Dieser Artikel enthält eine Übersicht über die Verwendung von Hintergrundaufgaben und beschreibt das Erstellen einer neuen Hintergrundaufgabe in einer WinUI 3-App. Informationen zum Migrieren Ihrer UWP-Apps mit Hintergrundaufgaben zu WinUI finden Sie in der Windows App SDK Background-Aufgabenmigrationsstrategie.

Hintergrundaufgaben sind App-Komponenten, die ohne Benutzeroberfläche im Hintergrund ausgeführt werden. Sie können Aktionen wie das Herunterladen von Dateien, das Synchronisieren von Daten, das Senden von Benachrichtigungen oder das Aktualisieren von Kacheln ausführen. Sie können durch verschiedene Ereignisse ausgelöst werden, z. B. Zeit, Systemänderungen, Benutzeraktionen oder Pushbenachrichtigungen. Diese Aufgaben können ausgeführt werden, wenn der entsprechende Trigger auch dann auftritt, wenn die App nicht ausgeführt wird.

Die Implementierung von Hintergrundaufgaben unterscheidet sich für UWP- und WinUI-Apps. Informationen zum Migrieren Ihrer UWP-Apps mit Hintergrundaufgaben zu WinUI finden Sie in der Windows App SDK Background-Aufgabenmigrationsstrategie.

Der Aufgabenplaner hilft Desktop-Apps dabei, die gleiche Funktionalität zu erzielen, die von BackgroundTaskBuilder in UWP-Apps bereitgestellt wird. Weitere Details zu Implementierungen mit TaskScheduler finden Sie hier.

Hintergrundaufgabe registrieren

Verwenden Sie die BackgroundTaskBuilder Klasse, die im Windows App SDK enthalten ist, um eine Hintergrundaufgabe zu registrieren, die voll vertrauenswürdige COM-Komponente verwendet.

Das folgende Beispiel zeigt, wie Sie eine Hintergrundaufgabe mit C++ registrieren. Im Windows App SDK github Beispiel sehen Sie diesen Registrierungscode in 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.
}

Das folgende Beispiel zeigt, wie Sie eine Hintergrundaufgabe mit C# registrieren. Im Windows App SDK github Beispiel sehen Sie diesen Registrierungscode in 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();

Beachten Sie, dass der Aufruf der Methode SetEntryPointClsid als Argument die GUID für eine von der App definierte Klasse erfordert, die IBackgroundTask implementiert. Diese Schnittstelle wird im Abschnitt "Implementieren von IBackgroundTask " weiter unten in diesem Artikel erläutert.

Bewährte Methoden für die Registrierung von Hintergrundaufgaben

Verwenden Sie beim Registrieren von Hintergrundaufgaben die folgenden bewährten Methoden.

  • Rufen Sie BackgroundExecutionManager.RequestAccessAsync auf, bevor Sie Hintergrundaufgaben registrieren.

  • Registrieren Sie eine Hintergrundaufgabe nicht mehrmals. Stellen Sie entweder sicher, dass eine Hintergrundaufgabe noch nicht registriert ist, bevor sie registriert wird, oder heben Sie, wie im Beispiel Windows App SDK, die Registrierung aller Hintergrundaufgaben auf, und registrieren Sie die Aufgaben dann erneut. Verwenden Sie die BackgroundTaskRegistration-Klasse , um vorhandene Hintergrundaufgaben abzufragen.

  • Verwenden Sie die BackgroundTaskBuilder.Name-Eigenschaft , um einen aussagekräftigen Namen für die Hintergrundaufgabe anzugeben, um das Debuggen und die Wartung zu vereinfachen.

Implementieren von IBackgroundTask

IBackgroundTask ist eine Schnittstelle, die eine Methode verfügbar macht, Run, die ausgeführt wird, wenn die Hintergrundaufgabe aufgerufen wird. Apps, die Hintergrundaufgaben verwenden, müssen eine Klasse enthalten, die IBackgroundTask implementiert.

Das folgende Beispiel zeigt, wie IBackgroundTask mit C++ implementiert wird. Im Windows App SDK github Beispiel sehen Sie diesen Registrierungscode in 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;
}

Das folgende Beispiel zeigt, wie IBackgroundTask mit C# implementiert wird. Im Windows App SDK github Beispiel sehen Sie diesen Registrierungscode in 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;
    }

Bewährte Methoden für die Implementierung von IBackgroundTask

Verwenden Sie beim Implementieren von IBackgroundTask die folgenden bewährten Methoden.

  • Wenn die Hintergrundaufgabe asynchrone Vorgänge ausführt, rufen Sie ein Verzögerungsobjekt ab, indem Sie GetDeferral für das in Run übergebene ITaskInstance-Objekt aufrufen. Dadurch wird verhindert, dass der Hintergrundaufgaben-Host vorzeitig beendet wird, backgroundtaskhost.exe bevor die Vorgänge abgeschlossen sind. Geben Sie den Aufschub frei, sobald alle asynchronen Aufgaben abgeschlossen sind.
  • Halten Sie Aufgaben so leicht wie möglich. Aufgaben, die über einen längeren Zeitraum ausgeführt werden, werden nicht empfohlen und können vom System beendet werden.
  • Verwenden Sie die Protokollierung, um Ausführungsdetails für die Problembehandlung zu erfassen.
  • Weitere bewährte Methoden für die Implementierung von Hintergrundaufgaben finden Sie in den Richtlinien für Hintergrundaufgaben.

Deklarieren der App-Erweiterung für Hintergrundaufgaben im App-Manifest

Sie müssen eine App-Erweiterung in der Datei Ihrer App Package.appxmanifest deklarieren, um Ihre Hintergrundaufgabe beim System zu registrieren, wenn Ihre App installiert ist, und um Informationen bereitzustellen, die das System benötigt, um die Hintergrundaufgabe zu starten.

Sie müssen ihrem App-Manifest eine Erweiterung mit der Kategorie "windows.backgroundTasks " hinzufügen, damit die Hintergrundaufgabe erfolgreich registriert werden kann, wenn die App installiert wird. C#-Apps müssen den EntryPoint-Attributwert "Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task" angeben. Bei C++-Apps wird dies automatisch hinzugefügt, indem WindowsAppSDKBackgroundTask auf true in der datei project festgelegt wird.

Sie müssen auch eine com:Extension mit dem Kategoriewert "windows.comServer" deklarieren. Sie müssen das LaunchAndActivationPermission-Attribut im com:ExeServer-Element angeben, um explizit die backgroundtaskhost.exe Prozessberechtigung zum Aufrufen der COM-Klasse zu erteilen. Informationen zum Format dieser Zeichenfolge finden Sie unter Security Descriptor String Format.

Stellen Sie sicher, dass die im com:Class-Element angegebene Klassen-ID mit der Klassen-ID für die Implementierung von IBackgroundTask übereinstimmt.

Das folgende Beispiel zeigt die Syntax der Anwendungserweiterungsdeklarationen in der App-Manifestdatei, damit das System eine Hintergrundaufgabe ermitteln und starten kann. Die vollständige App-Manifestdatei für die Hintergrundaufgabe auf github finden Sie unter 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>

Registrieren des COM-Servers für die Hintergrundaufgabe

Durch das Registrieren eines COM-Servers wird sichergestellt, dass das System weiß, wie Ihre Hintergrundaufgabenklasse instanziiert wird, wenn CoCreateInstance aufgerufen wird backgroundtaskhost.exe. Sie müssen die COM-Klassenfactory für Ihre Hintergrundaufgabe registrieren, indem Sie CoRegisterClassObject aufrufen, oder die COM-Aktivierung schlägt fehl.

COM-Serverregistrierung in C++

Das folgende Beispiel zeigt eine C++-Hilfsfunktion RegisterBackgroundTaskFactory , die die Klassenfactory für die Klasse registriert, die IBackgroundTask implementiert. In diesem Beispiel wird diese Klasse als BackgroundTask bezeichnet. Im Destruktor für die Hilfsklasse wird CoRevokeClassObject aufgerufen, um die Klassenfactory-Registrierung zu widerrufen.

Sie können diese Hilfsklasse im Beispiel-Repository in der Datei RegisterForCOM.cpp sehen.

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

Rufen Sie für die In-Prozess-Server-Registrierung in C++ die Hilfsklasse auf, um die Klassenfabrik innerhalb der Methode Application.OnLaunched zu registrieren. Sehen Sie sich den Aufruf der Hilfsmethode im Beispiel-Repository in App.xaml.cpp an.

void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
    window = make<MainWindow>();
    window.Activate();
    // Start COM server for the COM calls to complete
    comRegister.RegisterBackgroundTaskFactory();
}

Für out-of-proc-Hintergrundaufgaben muss die COM-Serverregistrierung während des Startvorgangs erfolgen. Sie können den Aufruf der Hilfsklasse in App.xaml.cpp sehen.

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-Serverregistrierung in C#

Das folgende Beispiel zeigt eine C#-Hilfsfunktion CreateInstance , die die Klassenfactory für die Klasse registriert, die IBackgroundTask implementiert. In diesem Beispiel wird diese Klasse als BackgroundTask bezeichnet. Die Hilfsklasse verwendet das LibraryImportAttribute, um auf die systemeigenen COM-Registrierungsmethoden aus C# zuzugreifen. Weitere Informationen finden Sie unter Quellgenerierung für Plattform-Aufrufe. Sie können die Implementierung der Hilfsklasse im Beispiel-Repository in ComServer.cs sehen.

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

Für inproc-Hintergrundaufgaben in C# wird die COM-Registrierung während des App-Starts im Konstruktor für das Application-Objekt ausgeführt. Sie können den Aufruf der Hilfsmethode im Beispiel-Repository in App.xaml.cs sehen.

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

Für out-of-proc-Aufgaben in C# müssen Sie die COM-Registrierung beim App-Start ausführen. Um dies zu tun, müssen Sie den standardmäßig XAML-generierten Main Einstiegspunkt deaktivieren, indem Sie die Projektdatei Ihrer App aktualisieren.

In der Standardvorlage project wird der Einstiegspunkt der Methode Main vom Compiler automatisch generiert. In diesem Beispiel wird die automatische Generierung von Main deaktiviert, sodass der erforderliche Aktivierungscode beim Start ausgeführt werden kann.

  1. Klicken Sie in Solution Explorer mit der rechten Maustaste auf das Symbol project, und wählen Sie "Project Datei bearbeiten" aus.
  2. Fügen Sie im PropertyGroup-Element das folgende untergeordnete Element hinzu, um die automatisch generierte Hauptfunktion zu deaktivieren.
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

Sie können im Beispiel-Repository in Program.cs den Aufruf der Hilfsklasse sehen, um die COM-Klasse zu registrieren.

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