Condividi tramite


Uso delle attività in background nelle app di Windows

Questo articolo offre una panoramica dell'uso delle attività in background e descrive come creare una nuova attività in background in un'app WinUI 3. Per informazioni sulla migrazione delle app UWP con attività in background a WinUI, vedi la strategia di migrazione delle attività Windows App SDK Background.

Le attività in background sono componenti dell'app eseguiti in background senza un'interfaccia utente. Possono eseguire azioni come il download di file, la sincronizzazione dei dati, l'invio di notifiche o l'aggiornamento dei riquadri. Possono essere attivati da vari eventi, ad esempio l'ora, le modifiche di sistema, le azioni utente o le notifiche push. Queste attività possono essere eseguite quando si verifica un trigger corrispondente anche quando l'app non è in esecuzione.

L'implementazione delle attività in background è diversa per le app UWP e WinUI. Per informazioni sulla migrazione delle app UWP con attività in background a WinUI, vedi la strategia di migrazione delle attività Windows App SDK Background.

Utilità di pianificazione consente alle app desktop di ottenere le stesse funzionalità fornite da BackgroundTaskBuilder nelle app UWP. Altre informazioni sulle implementazioni che usano TaskScheduler sono disponibili qui.

Registrare un'attività in background

Usare la classe BackgroundTaskBuilder inclusa nel Windows App SDK per registrare un'attività in background che usa il componente COM con attendibilità totale.

L'esempio seguente illustra come registrare un'attività in background usando C++. Nell'esempio di Windows App SDK github è possibile visualizzare questo codice di registrazione 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.
}

L'esempio seguente illustra come registrare un'attività in background usando C#. Nell'esempio di Windows App SDK github è possibile visualizzare questo codice di registrazione 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();

Si noti che la chiamata al metodo SetEntryPointClsid accetta come argomento il GUID per una classe definita dall'app che implementa IBackgroundTask. Questa interfaccia è descritta nella sezione Implementare IBackgroundTask più avanti in questo articolo.

Procedure consigliate per la registrazione delle attività in background

Usare le procedure consigliate seguenti per la registrazione delle attività in background.

  • Chiamare BackgroundExecutionManager.RequestAccessAsync prima di registrare le attività in background.

  • Non registrare più volte un'attività in background. Verificare che un'attività in background non sia già registrata prima della registrazione o, come nell'esempio di Windows App SDK, annullare la registrazione di tutte le attività in background e quindi registrare nuovamente le attività. Usare la classe BackgroundTaskRegistration per eseguire query per le attività in background esistenti.

  • Utilizzare la proprietà BackgroundTaskBuilder.Name per specificare un nome significativo per l'attività in background per semplificare il debug e la manutenzione.

Implementare IBackgroundTask

IBackgroundTask è un'interfaccia che espone un metodo, Run, che viene eseguito quando viene richiamata l'attività in background. Le app che usano attività in background devono includere una classe che implementa IBackgroundTask.

L'esempio seguente illustra come implementare IBackgroundTask usando C++. Nell'esempio di Windows App SDK github è possibile visualizzare questo codice di registrazione 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;
}

L'esempio seguente illustra come implementare IBackgroundTask usando C#. Nell'esempio di Windows App SDK github è possibile visualizzare questo codice di registrazione 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;
    }

Procedure consigliate per l'implementazione di IBackgroundTask

Usare le procedure consigliate seguenti per l'implementazione di IBackgroundTask.

  • Se l'attività in background eseguirà operazioni asincrone, ottenere un oggetto differitore chiamando GetDeferral sull'oggetto ITaskInstance passato in Run. Ciò impedisce all'host dell'attività in background, backgroundtaskhost.exe, di terminare prematuramente prima del completamento delle operazioni. Rilasciare il differimento una volta completate tutte le attività asincrone.
  • Mantenere le attività il più leggero possibile. Le attività a esecuzione prolungata possono essere terminate dal sistema e non sono consigliate.
  • Usare la registrazione per acquisire i dettagli di esecuzione per la risoluzione dei problemi.
  • Per altre procedure consigliate per l'implementazione delle attività in background, vedere Linee guida per le attività in background.

Dichiarare l'estensione dell'app per le attività in background nel manifesto dell'app

Devi dichiarare un'estensione dell'app nel file dell'app Package.appxmanifest per registrare l'attività in background con il sistema quando l'app è installata e fornire informazioni necessarie al sistema per avviare l'attività in background.

Devi aggiungere un'estensione con la categoria windows.backgroundTasks al manifesto dell'app affinché l'attività in background venga registrata correttamente quando l'app è installata. Le app C# devono specificare il valore dell'attributo EntryPoint "Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task". Per le app C++, questo viene aggiunto automaticamente impostando WindowsAppSDKBackgroundTask su true nel file project.

Devi anche dichiarare com:Extension con il valore di categoria "windows.comServer". Per richiamare la classe COM, è necessario specificare l'attributo LaunchAndActivationPermission nell'elemento backgroundtaskhost.exe. Per informazioni sul formato di questa stringa, vedere Security Descriptor String Format.

Assicurarsi che l'ID classe specificato nell'elemento com:Class corrisponda all'ID classe per l'implementazione di IBackgroundTask.

L'esempio seguente mostra la sintassi delle dichiarazioni di estensione dell'applicazione nel file manifesto dell'app per consentire al sistema di individuare e avviare un'attività in background. Per vedere il file manifesto completo dell'app per l'attività in background su GitHub, vedere 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>

Registrare il server COM per l'attività in background

La registrazione di un server COM garantisce che il sistema sappia come istanziare la classe della tua attività in background quando il componente CoCreateInstance viene chiamato da backgroundtaskhost.exe. È necessario registrare la class factory COM per l'attività in background chiamando CoRegisterClassObject o l'attivazione COM avrà esito negativo.

Registrazione del server COM in C++

L'esempio seguente mostra una funzione helper C++, RegisterBackgroundTaskFactory che registra la class factory per la classe che implementa IBackgroundTask. In questo esempio questa classe è denominata BackgroundTask. Nel distruttore per la classe di supporto, viene chiamato CoRevokeClassObject per revocare la registrazione della class factory.

È possibile visualizzare questa classe helper nel repository di esempio nel file 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);
    }
}

Per la registrazione del server in-process in C++, chiamare la classe di supporto per registrare la factory della classe all'interno del metodo Application.OnLaunched. Vedere la chiamata al metodo helper nel repository di esempio in 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();
}

Per le attività in background out-of-process, è necessario eseguire la registrazione del server COM durante il processo di avvio. È possibile visualizzare la chiamata alla classe helper in 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;
}

Registrazione del server COM in C#

L'esempio seguente illustra una funzione helper C#, CreateInstance che registra la class factory per la classe che implementa IBackgroundTask. In questo esempio questa classe è denominata BackgroundTask. La classe helper usa il LibraryImportAttribute per accedere ai metodi di registrazione nativi di COM da C#. Per ulteriori informazioni, vedere Generazione di codice per invocazioni della piattaforma. È possibile visualizzare l'implementazione della classe helper nel repository di esempio in 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;
    }
}

Per le attività in background in C#, la registrazione COM viene eseguita durante l'avvio dell'app nel costruttore per l'oggetto Application . È possibile visualizzare la chiamata al metodo helper nel repository di esempio in 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);
}

Per le attività out-of-proc in C#, è necessario eseguire la registrazione COM all'avvio dell'app. Per fare ciò, devi disabilitare il punto di ingresso predefinito generato da XAML Main aggiornando il file di progetto dell'app.

Nel modello di project predefinito il punto di ingresso del metodo Main viene generato automaticamente dal compilatore. Questo esempio disabiliterà la generazione automatica di Main in modo che il codice di attivazione necessario possa essere eseguito all'avvio.

  1. In Solution Explorer fare clic con il pulsante destro del mouse sull'icona project e scegliere Modifica Project File.
  2. Nell'elemento PropertyGroup aggiungere l'elemento figlio seguente per disabilitare la funzione principale generata automaticamente.
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

È possibile visualizzare la chiamata alla classe helper per registrare la classe COM nel repository di esempio in 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();
    }
}