Servicios de trabajo en .NET

Hay numerosas razones para crear servicios de ejecución larga, como:

  • Procesamiento de datos de uso intensivo de CPU.
  • Cola de elementos de trabajo en segundo plano
  • Realización de una operación basada en el tiempo según una programación

Normalmente, el procesamiento de servicios en segundo plano no implica una interfaz de usuario (UI), pero se pueden crear interfaces de usuario en torno a ellos. En los inicios de .NET Framework, los desarrolladores de Windows podían crear servicios de Windows para estos fines. Ahora, con .NET, puede usar BackgroundService, que es una implementación de IHostedService, o implementar el suyo propio.

Con .NET, ya no está limitado a Windows. Puede desarrollar servicios en segundo plano multiplataforma. Los servicios hospedados están listos para el registro, la configuración y la inserción de dependencias (ID). Forman parte del conjunto de extensiones de bibliotecas, lo que significa que son fundamentales para todas las cargas de trabajo de .NET que funcionan con el host genérico.

Importante

La instalación del SDK de .NET también instala Microsoft.NET.Sdk.Worker y la plantilla de trabajo. Es decir, después de instalar el SDK de .NET, puede crear un nuevo trabajo (“new worker”) mediante el comando dotnet new worker. Si usa Visual Studio, la plantilla se oculta hasta que se instala la carga de trabajo opcional de ASP.NET y desarrollo web.

Terminología

Muchos términos se usan erróneamente como sinónimos. En esta sección se definen algunos de estos términos para que su intención en este artículo sea más evidente.

  • Servicio en segundo plano: el tipo BackgroundService.
  • Servicio hospedado: implementaciones de IHostedService o del propio IHostedService.
  • Servicio de ejecución larga: cualquier servicio que se ejecuta continuamente.
  • Windows Service: la infraestructura de Windows Service, originalmente basada en .NET Framework, a la que se puede acceder ahora desde .NET.
  • Worker Service: la plantilla Servicio de trabajo.

Plantilla Worker Service

La plantilla Servicio de trabajo está disponible para la CLI de .NET y Visual Studio. Para más información, vea la plantilla dotnet new worker de la CLI de .NET. La plantilla consta de una clase Program y Worker.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

La clase Program anterior:

  • Crea una interfaz HostApplicationBuilder.
  • Llama AddHostedService a para registrar Worker como un servicio hospedado.
  • Compila una interfaz IHost a partir del generador.
  • Llama a Run en la instancia de host, que ejecuta la aplicación.

Valores predeterminado de plantilla

La plantilla del rol de trabajo no habilita la recolección de elementos no utilizados del servidor (GC) de forma predeterminada, ya que hay numerosos factores que desempeñan un papel para determinar su necesidad. Todos los escenarios que requieren servicios de ejecución prolongada deben tener en cuenta las implicaciones de rendimiento de este valor predeterminado. Para habilitar la recolección de elementos no utilizados del servidor, agregue el nodo ServerGarbageCollection al archivo del proyecto:

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Ventajas y consideraciones

habilitado Disabled
Administración eficaz de memoria: reclama automáticamente memoria no utilizada para evitar pérdidas de memoria y optimizar el uso de recursos. Rendimiento mejorado en tiempo real: evita posibles pausas o interrupciones causadas por la recolección de elementos no utilizados en aplicaciones sensibles a la latencia.
Estabilidad a largo plazo: ayuda a mantener un rendimiento estable en servicios de larga duración mediante la administración de memoria durante períodos prolongados. Eficiencia de los recursos: puede conservar los recursos de CPU y memoria en entornos con restricción de recursos.
Mantenimiento reducido: minimiza la necesidad de administración manual de memoria, lo que simplifica el mantenimiento. Control de memoria manual: proporciona un control específico sobre la memoria para aplicaciones especializadas.
Comportamiento predecible: contribuye al comportamiento de aplicación coherente y predecible. Adecuado para procesos de corta duración: minimiza la sobrecarga de recolección de elementos no utilizados para procesos efímeros o de corta duración.

Para obtener más información sobre las consideraciones de rendimiento, vea Recolección de elementos no utilizados del servidor. Para obtener más información sobre cómo configurar la recolección de elementos no utilizados del servidor, vea Ejemplos de configuración de la recolección de elementos no utilizados del servidor.

Clase del rol de trabajo

En cuanto a Worker, la plantilla proporciona una implementación sencilla.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

La clase Worker anterior es una subclase de BackgroundService, que implementa IHostedService. BackgroundService es abstract class y requiere la subclase para implementar BackgroundService.ExecuteAsync(CancellationToken). En la implementación de la plantilla, los bucles ExecuteAsync se recorren una vez por segundo, registrando la fecha y hora actuales hasta que se señale el proceso para la cancelación.

El archivo del proyecto

La plantilla Worker se basa en el siguiente archivo del proyecto Sdk:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Para más información, vea SDK de proyectos de .NET.

Paquete de NuGet

Una aplicación basada en la plantilla Worker usa el SDK de Microsoft.NET.Sdk.Worker y tiene una referencia de paquete explícita al paquete Microsoft.Extensions.Hosting.

Contenedores y adaptabilidad a la nube

Con las cargas de trabajo de .NET más modernas, los contenedores son una opción viable. Al crear un servicio de ejecución larga a partir de la plantilla de Worker en Visual Studio, puede optar por la compatibilidad con Docker. Al hacerlo, se crea un Dockerfile que contiene la aplicación .NET. Un Dockerfile es un conjunto de instrucciones para compilar una imagen. En el caso de las aplicaciones .NET, el Dockerfile normalmente se encuentra en la raíz del directorio junto a un archivo de solución.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

Los pasos anteriores del Dockerfile incluyen:

  • Establecer la imagen base de mcr.microsoft.com/dotnet/runtime:8.0 como el alias base.
  • Cambiar el directorio de trabajo a /app.
  • Establecer el alias build de la imagen mcr.microsoft.com/dotnet/sdk:8.0.
  • Cambiar el directorio de trabajo a /src.
  • Copiar el contenido y publicar la aplicación .NET:
  • Retransmitir la imagen del SDK de .NET desde mcr.microsoft.com/dotnet/runtime:8.0 (el alias base).
  • Copiar la salida de compilación publicada de /publish.
  • Definir el punto de entrada, que delega en dotnet App.BackgroundService.dll.

Sugerencia

MRC en mcr.microsoft.com significa "Microsoft Container Registry", y es el catálogo de contenedores sindicados de Microsoft del centro de Docker oficial. El artículo Catálogo de contenedores sindicados de Microsoft contiene información adicional.

Al establecer Docker como destino de una estrategia de implementación para la plantilla de Worker Service de .NET, hay algunas consideraciones en el archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
  </ItemGroup>
</Project>

En el archivo del proyecto anterior, el elemento <DockerDefaultTargetOS> especifica Linux como destino. Para establecer los contenedores de Windows como destino, use Windows en su lugar. El paquete de NuGet Microsoft.VisualStudio.Azure.Containers.Tools.Targets se agrega automáticamente como una referencia de paquete cuando se selecciona la compatibilidad con Docker en la plantilla.

Para más información sobre Docker con .NET, vea Tutorial: Incluir una aplicación de .NET Core en un contenedor. Para más información sobre la implementación en Azure, vea Tutorial: Implementación de una plantilla Worker Service en Azure.

Importante

Si quiere utilizar los secretos de usuario con la plantilla Worker, tendría que hacer referencia explícitamente al paquete NuGet Microsoft.Extensions.Configuration.UserSecrets.

Extensibilidad del servicio hospedado

La interfaz IHostedService define dos métodos:

Estos dos métodos sirven como métodos de ciclo de vida: se invocan durante los eventos de inicio y de detección del host, respectivamente.

Nota:

Al invalidar los métodos StartAsync o StopAsync, debe llamar y usar await con el método de clase base para garantizar que el servicio se inicia o se cierra adecuadamente.

Importante

La interfaz actúa como una restricción de parámetro de tipo genérico en el método de extensión AddHostedService<THostedService>(IServiceCollection), lo que significa que solo se permiten implementaciones. Puede usar la clase BackgroundService proporcionada con una subclase, o bien implementar la suya propia por completo.

Finalización de señal

En los escenarios más comunes, no es necesario indicar explícitamente la finalización de un servicio hospedado. Cuando el host inicia los servicios, están diseñados para ejecutarse hasta que se detenga el host. Sin embargo, en algunos escenarios, es posible que tenga que indicar la finalización de toda la aplicación host cuando se complete el servicio. Para señalar la finalización, considere el uso de la siguiente clase Worker:

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

En el código anterior, el método ExecuteAsync no realiza un bucle y, cuando se completa, llama a IHostApplicationLifetime.StopApplication().

Importante

Esto indicará al host que debe detenerse y sin esta llamada al StopApplication host seguirá ejecutándose indefinidamente.

Para más información, consulte:

Consulte también