Compartir a través de


Uso de tareas en segundo plano en aplicaciones de Windows

En este artículo se proporciona información general sobre el uso de tareas en segundo plano y se describe cómo crear una nueva tarea en segundo plano en una aplicación winUI 3. Para obtener información sobre cómo migrar las aplicaciones para UWP con tareas en segundo plano a WinUI, consulta la estrategia de migración de tareas en segundo plano Windows App SDK.

Las tareas en segundo plano son componentes de la aplicación que se ejecutan en segundo plano sin una interfaz de usuario. Pueden realizar acciones como descargar archivos, sincronizar datos, enviar notificaciones o actualizar iconos. Pueden desencadenarse mediante varios eventos, como la hora, los cambios del sistema, las acciones del usuario o las notificaciones push. Estas tareas se pueden ejecutar cuando se produce el desencadenador correspondiente incluso cuando la aplicación no está en estado de ejecución.

La implementación de tareas en segundo plano es diferente para las aplicaciones para UWP y WinUI. Para obtener información sobre cómo migrar las aplicaciones para UWP con tareas en segundo plano a WinUI, consulta la estrategia de migración de tareas en segundo plano Windows App SDK.

El Programador de tareas ayuda a las aplicaciones de escritorio a lograr la misma funcionalidad que proporciona BackgroundTaskBuilder en aplicaciones para UWP. Aquí encontrará más detalles sobre las implementaciones que usan TaskScheduler.

Registrar una tarea en segundo plano

Use la clase BackgroundTaskBuilder incluida con el SDK de aplicaciones de Windows para registrar una tarea en segundo plano que use un componente COM de plena confianza.

En el ejemplo siguiente se muestra cómo registrar una tarea en segundo plano mediante C++. En el ejemplo de GitHub de Windows App SDK, puede ver este código de registro en 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.
}

En el ejemplo siguiente se muestra cómo registrar una tarea en segundo plano mediante C#. En el ejemplo de GitHub del SDK de aplicaciones de Windows, puede ver este código de registro en 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();

Tenga en cuenta que la llamada al método SetEntryPointClsid toma como argumento el GUID de una clase definida por la aplicación que implementa IBackgroundTask. Esta interfaz se describe en la sección Implementar IBackgroundTask más adelante en este artículo.

Procedimientos recomendados para el registro de tareas en segundo plano

Use los siguientes procedimientos recomendados al registrar tareas en segundo plano.

  • Llame a BackgroundExecutionManager.RequestAccessAsync antes de registrar tareas en segundo plano.

  • No registre una tarea en segundo plano varias veces. Compruebe que una tarea en segundo plano aún no está registrada antes de registrarse o, como en el ejemplo de Windows App SDK, anule el registro de todas las tareas en segundo plano y vuelva a registrar las tareas. Use la clase BackgroundTaskRegistration para consultar las tareas en segundo plano existentes.

  • Use la propiedad BackgroundTaskBuilder.Name para especificar un nombre significativo para la tarea en segundo plano para simplificar la depuración y el mantenimiento.

Implementación de IBackgroundTask

IBackgroundTask es una interfaz que expone un método, Run, que se ejecuta cuando se invoca la tarea en segundo plano. Las aplicaciones que usan tareas en segundo plano deben incluir una clase que implemente IBackgroundTask.

En el ejemplo siguiente se muestra cómo implementar IBackgroundTask mediante C++. En el ejemplo de GitHub de Windows App SDK, puede ver este código de registro en 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;
}

En el ejemplo siguiente se muestra cómo implementar IBackgroundTask mediante C#. En el ejemplo de GitHub de Windows App SDK, puede ver este código de registro en 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;
    }

Procedimientos recomendados para implementar IBackgroundTask

Use los siguientes procedimientos recomendados al implementar IBackgroundTask.

  • Si la tarea en segundo plano realizará operaciones asincrónicas, obtenga un objeto de aplazamiento llamando a GetDeferral en el objeto ITaskInstance pasado a Run. Esto impide que el host de tareas en segundo plano, backgroundtaskhost.exe, finalice prematuramente antes de que se completen las operaciones. Libere el aplazamiento una vez que todas las tareas asincrónicas hayan sido completadas.
  • Mantenga las tareas lo más ligeras posible. El sistema puede finalizar tareas que tardan mucho tiempo y no se aconsejan.
  • Use el registro para capturar los detalles de ejecución para solucionar problemas.
  • Para obtener más procedimientos recomendados para implementar tareas en segundo plano, consulte Directrices para tareas en segundo plano.

Declara la extensión de la aplicación de tareas en segundo plano en el App Manifest

Debes declarar una extensión de aplicación en el archivo de la Package.appxmanifest aplicación para registrar tu tarea en segundo plano con el sistema cuando la aplicación esté instalada y proporcionar información que el sistema necesita para iniciar la tarea en segundo plano.

Debes agregar una extensión con la categoría windows.backgroundTasks al manifiesto de la aplicación para que la tarea en segundo plano se registre correctamente cuando se instale la aplicación. Las aplicaciones de C# deben especificar el valor del atributo EntryPoint "Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.Task". Para las aplicaciones de C++, esto se agrega automáticamente estableciendo WindowsAppSDKBackgroundTasken true en el archivo del proyecto.

También debe declarar una com:Extension con el valor de categoría de "windows.comServer". Debe especificar el atributo LaunchAndActivationPermission en el elemento com:ExeServer para conceder explícitamente el backgroundtaskhost.exe permiso de proceso para invocar la clase COM. Para obtener información sobre el formato de esta cadena, vea Formato de cadena del descriptor de seguridad.

Asegúrese de que el identificador de clase que especifique en el elemento com:Class coincide con el identificador de clase de la implementación de IBackgroundTask.

En el ejemplo siguiente se muestra la sintaxis de las declaraciones de extensión de aplicación en el archivo de manifiesto de aplicación para permitir que el sistema detecte e inicie una tarea en segundo plano. Para ver el archivo de manifiesto de la aplicación completa para la tarea en segundo plano en github, consulte 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>

Registrar el servidor COM para la tarea en segundo plano

El registro de un servidor COM garantiza que el sistema sepa cómo crear instancias de la clase de tareas en segundo plano cuando CoCreateInstance es llamado por backgroundtaskhost.exe. Debe registrar el generador de clases COM para la tarea en segundo plano llamando a CoRegisterClassObject o la activación COM producirá un error.

Registro de servidor COM en C++

En el ejemplo siguiente se muestra una función auxiliar de C++, RegisterBackgroundTaskFactory que registra el generador de clases para la clase que implementa IBackgroundTask. En este ejemplo, esta clase se denomina BackgroundTask. En el destructor de la clase auxiliar, se llama a CoRevokeClassObject para revocar el registro del generador de clases.

Puede ver esta clase auxiliar en el repositorio de ejemplo en el archivo 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);
    }
}

Para el registro del servidor in-proc en C++, llame a la clase auxiliar para registrar la fábrica de clases desde el método Application.OnLaunched. Consulte la llamada al método auxiliar en el repositorio de ejemplo en 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();
}

Para las tareas en segundo plano que son fuera de proceso, el registro del servidor COM debe realizarse durante el proceso de inicio. Puede ver la llamada a la clase auxiliar en 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;
}

Registro de servidor COM en C#

En el ejemplo siguiente se muestra una función auxiliar de C#, CreateInstance que registra el generador de clases para la clase que implementa IBackgroundTask. En este ejemplo, esta clase se denomina BackgroundTask. La clase auxiliar usa LibraryImportAttribute para acceder a los métodos de registro COM nativos desde C#. Para obtener más información, consulte Generación de origen para invocaciones de plataforma. Puede ver la implementación de la clase auxiliar en el repositorio de ejemplo en 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;
    }
}

Para las tareas en segundo plano en proceso en C#, el registro COM se realiza durante el inicio de la aplicación, en el constructor del objeto Application . Puede ver la llamada al método auxiliar en el repositorio de ejemplo en 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);
}

Para las tareas fuera de proceso en C#, debe realizar el registro COM en el inicio de la aplicación. Para ello, debes deshabilitar el punto de entrada Main generado por XAML predeterminado actualizando el archivo de proyecto de la aplicación.

En la plantilla de proyecto predeterminada, el compilador genera automáticamente el punto de entrada del método Main . En este ejemplo se deshabilitará la autogeneración de Main para que se pueda ejecutar el código de activación necesario en el inicio.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el icono del proyecto y seleccione Editar archivo de proyecto.
  2. En el elemento PropertyGroup , agregue el siguiente elemento secundario para deshabilitar la función principal generada automáticamente.
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

Puede ver la llamada a la clase auxiliar para registrar la clase COM en el repositorio de ejemplo en 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();
    }
}