Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Existen numerosas razones para crear servicios de larga duración como:
- Procesamiento de datos de alta carga de CPU.
- Cola de elementos de trabajo en segundo plano
- Realizar una operación basada en el tiempo según una programación.
Normalmente, el procesamiento del servicio en segundo plano no implica una interfaz de usuario (UI), pero las interfaces de usuario se pueden crear en torno a ellas. En los primeros días con .NET Framework, los desarrolladores de Windows podrían crear servicios de Windows para estos fines. Ahora con .NET, puede usar BackgroundService, que es una implementación de IHostedService, o implementar su propia.
Con .NET, ya no está restringido a Windows. Puede crear servicios de fondo multiplataforma. Los servicios hospedados están listos para el registro, la configuración y la inserción de dependencias (DI). 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, puedes crear un nuevo trabajador mediante el comando dotnet new worker. Si usa Visual Studio, la plantilla se oculta hasta que se instale la carga de trabajo opcional 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: tipo BackgroundService .
- Servicio hospedado: implementaciones de IHostedService o el propio IHostedService.
- Servicio de larga duración: Cualquier servicio que se ejecute continuamente.
- Servicio de Windows: la infraestructura del servicio de Windows , centrada originalmente en .NET Framework, pero ahora accesible a través de .NET.
- Servicio de Trabajos: la plantilla Servicio de Trabajos.
Plantilla Worker Service
La plantilla de servicio de trabajo está disponible en la CLI de .NET y en el Visual Studio. Para más información, vea la plantilla dotnet new worker
de la CLI de .NET. La plantilla consta de una Program
clase y Worker
.
using App.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
La clase anterior Program
:
- Crea una interfaz HostApplicationBuilder.
- Llama AddHostedService para registrar el
Worker
como un servicio hospedado. - Compila una interfaz IHost a partir del generador.
- Invoca
Run
en la instanciahost
, que ejecuta la aplicación.
Valores predeterminados 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 larga duración deben tener en cuenta las implicaciones de rendimiento de este valor predeterminado. Para habilitar el GC del servidor, agregue el nodo ServerGarbageCollection
al archivo del proyecto.
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
Ventajas y consideraciones
Activado | Deshabilitado |
---|---|
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 la recolección de basura para procesos efímeros o transitorios. |
Para obtener más información sobre las consideraciones de rendimiento, consulte Gc del servidor. Para obtener más información sobre cómo configurar gc del servidor, consulte Ejemplos de configuración de GC de servidor.
Clase de trabajador
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 anterior Worker
es una subclase BackgroundServicede IHostedService, que implementa . BackgroundService es un abstract class
y requiere que la subclase implemente 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 de trabajo se basa en el siguiente archivo Sdk
de proyecto :
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para más información, consulte SDK de proyectos de .NET.
Paquete de NuGet
Una aplicación basada en la plantilla de trabajo usa el Microsoft.NET.Sdk.Worker
SDK y tiene una referencia de paquete explícita al paquete Microsoft.Extensions.Hosting .
Contenedores y capacidad de adaptación en la nube
Con la mayoría de las cargas de trabajo de .NET 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@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 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 de Dockerfile incluyen:
- Establecer la imagen base de
mcr.microsoft.com/dotnet/runtime:8.0
como aliasbase
. - Cambiar el directorio de trabajo a /app.
- Establecer el alias
build
de la imagenmcr.microsoft.com/dotnet/sdk:8.0
. - Cambiar el directorio de trabajo a /src.
- Copia del contenido y publicación de la aplicación .NET:
- La aplicación se publica mediante el
dotnet publish
comando .
- La aplicación se publica mediante el
- Retransmitir la imagen del SDK de .NET desde
mcr.microsoft.com/dotnet/runtime:8.0
(el aliasbase
). - Copiar la salida de compilación publicada de /publish.
- Definir el punto de entrada, que delega en
dotnet App.BackgroundService.dll
.
Sugerencia
McR en mcr.microsoft.com
significa "Microsoft Container Registry" y es el catálogo de contenedores sindicado de Microsoft del centro oficial de Docker. El artículo catálogo de contenedores de sindicatos de Microsoft contiene detalles adicionales.
Al dirigirse a Docker como estrategia de implementación para el servicio de trabajo de .NET, hay algunas consideraciones en el archivo de 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="9.0.6" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>
</Project>
En el archivo de proyecto anterior, el <DockerDefaultTargetOS>
elemento especifica Linux
como destino. Para tener como destino contenedores de Windows, use Windows
en su lugar. El Microsoft.VisualStudio.Azure.Containers.Tools.Targets
paquete NuGet se agrega automáticamente como referencia de paquete cuando se selecciona la compatibilidad con Docker en la plantilla.
Para obtener más información sobre Docker con .NET, consulte Tutorial: Containerize a .NET app (Tutorial: Contenedorización de una aplicación .NET). Para más información sobre la implementación en Azure, consulte Tutorial: Implementación de un servicio de trabajo 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 les llama durante los eventos de inicio y detenció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 AddHostedService<THostedService>(IServiceCollection) método de extensión, lo que significa que solo se permiten implementaciones. Puede usar la clase proporcionada BackgroundService con una subclase o 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 indicar la finalización, tenga en cuenta la siguiente Worker
clase:
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 BackgroundService.ExecuteAsync(CancellationToken) método 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. Si tiene previsto ejecutar un servicio hospedado de corta duración (escenario de ejecución única) y desea usar la plantilla Worker, debe llamar a StopApplication
para indicar al host que se detenga.
Para obtener más información, consulte:
- Host genérico de .NET: IHostApplicationLifetime
- Host genérico de .NET: apagado del host
- Host genérico de .NET: proceso de apagado de hospedaje
Enfoque alternativo
Para una aplicación de corta duración que necesite inserción de dependencias, registro y configuración, use el host genérico de .NET en lugar de la plantilla de trabajo. Esto le permite usar estas características sin la Worker
clase . Un ejemplo sencillo de una aplicación de corta duración mediante el host genérico podría definir un archivo de proyecto como el siguiente:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>ShortLived.App</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
</ItemGroup>
</Project>
Es posible que la Program
clase tenga un aspecto similar al siguiente:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();
using var host = builder.Build();
try
{
var runner = host.Services.GetRequiredService<JobRunner>();
await runner.RunAsync();
return 0; // success
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Unhandled exception occurred during job execution.");
return 1; // failure
}
El código anterior crea un JobRunner
servicio, que es una clase personalizada que contiene la lógica para que se ejecute el trabajo. Se llama al método RunAsync
en JobRunner
, y si se completa correctamente, la aplicación devuelve 0
. Si se produce una excepción no controlada, registra el error y devuelve 1
.
En este escenario sencillo, la JobRunner
clase podría tener este aspecto:
using Microsoft.Extensions.Logging;
internal sealed class JobRunner(ILogger<JobRunner> logger)
{
public async Task RunAsync()
{
logger.LogInformation("Starting job...");
// Simulate work
await Task.Delay(1000);
// Simulate failure
// throw new InvalidOperationException("Something went wrong!");
logger.LogInformation("Job completed successfully.");
}
}
Obviamente, tendría que agregar lógica real al RunAsync
método , pero en este ejemplo se muestra cómo usar el host genérico para una aplicación de corta duración sin necesidad de una Worker
clase y sin necesidad de indicar explícitamente la finalización del host.
Consulte también
- Tutoriales de la subclase BackgroundService:
- Implementación personalizada IHostedService :