Tareas en segundo plano con servicios hospedados en ASP.NET CoreBackground tasks with hosted services in ASP.NET Core
Por Jeow Li HuanBy Jeow Li Huan
En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. En este tema se incluyen tres ejemplos de servicio hospedado:This topic provides three hosted service examples:
- Una tarea en segundo plano que se ejecuta según un temporizador.Background task that runs on a timer.
- Un servicio hospedado que activa un servicio con ámbito.Hosted service that activates a scoped service. El servicio con ámbito puede usar la inserción de dependencias (DI).The scoped service can use dependency injection (DI).
- Tareas en segundo plano en cola que se ejecutan en secuencia.Queued background tasks that run sequentially.
Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)
Plantilla Worker ServiceWorker Service template
La plantilla Worker Service de ASP.NET Core sirve de punto de partida para escribir aplicaciones de servicio de larga duración.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Una aplicación creada a partir de la plantilla Worker Service especifica el SDK de trabajo en su archivo del proyecto:An app created from the Worker Service template specifies the Worker SDK in its project file:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar la plantilla como base de una aplicación de servicios hospedados:To use the template as a basis for a hosted services app:
- Visual StudioVisual Studio
- Visual Studio para MacVisual Studio for Mac
- CLI de .NET Core.NET Core CLI
- Cree un nuevo proyecto.Create a new project.
- Seleccione Worker Service (Servicio de Worker).Select Worker Service. Seleccione Siguiente.Select Next.
- Proporcione un nombre para el proyecto en el campo Nombre del proyecto o acepte el predeterminado.Provide a project name in the Project name field or accept the default project name. Seleccione Crear.Select Create.
- En el cuadro de diálogo Crear un servicio de Worker, seleccione Crear.In the Create a new Worker service dialog, select Create.
PackagePackage
Una aplicación basada en la plantilla Worker Service usa el SDK de Microsoft.NET.Sdk.Worker
y tiene una referencia de paquete explícita al paquete Microsoft.Extensions.Hosting.An app based on the Worker Service template uses the Microsoft.NET.Sdk.Worker
SDK and has an explicit package reference to the Microsoft.Extensions.Hosting package. Por ejemplo, consulte el archivo del proyecto de la aplicación de ejemplo (BackgroundTasksSample.csproj).For example, see the sample app's project file (BackgroundTasksSample.csproj).
En el caso de las aplicaciones web que usan el SDK de Microsoft.NET.Sdk.Web
, desde el marco compartido se hace una referencia implícita al paquete Microsoft.Extensions.Hosting.For web apps that use the Microsoft.NET.Sdk.Web
SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. No se requiere una referencia de paquete explícita en el archivo del proyecto de la aplicación.An explicit package reference in the app's project file isn't required.
Interfaz IHostedServiceIHostedService interface
La interfaz IHostedService define dos métodos para los objetos administrados por el host:The IHostedService interface defines two methods for objects that are managed by the host:
StartAsync(CancellationToken):
StartAsync
contiene la lógica para iniciar la tarea en segundo plano.StartAsync(CancellationToken):StartAsync
contains the logic to start the background task. Se llama aStartAsync
antes de que:StartAsync
is called before:- La canalización de procesamiento de solicitudes de la aplicación está configurada (
Startup.Configure
).The app's request processing pipeline is configured (Startup.Configure
). - El servidor se haya iniciado y IApplicationLifetime.ApplicationStarted se haya activado.The server is started and IApplicationLifetime.ApplicationStarted is triggered.
El comportamiento predeterminado se puede cambiar para que el
StartAsync
del servicio hospedado se ejecute después de que se haya configurado la canalización de la aplicación y se haya llamado aApplicationStarted
.The default behavior can be changed so that the hosted service'sStartAsync
runs after the app's pipeline has been configured andApplicationStarted
is called. Para cambiar el comportamiento predeterminado, agregue el servicio hospedado (VideosWatcher
en el ejemplo siguiente) después de llamar aConfigureWebHostDefaults
:To change the default behavior, add the hosted service (VideosWatcher
in the following example) after callingConfigureWebHostDefaults
:using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .ConfigureServices(services => { services.AddHostedService<VideosWatcher>(); }); }
- La canalización de procesamiento de solicitudes de la aplicación está configurada (
StopAsync(CancellationToken): se desencadena cuando el host está realizando un cierre estable.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown.
StopAsync
contiene la lógica para finalizar la tarea en segundo plano.StopAsync
contains the logic to end the background task. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.El token de cancelación tiene un tiempo de espera predeterminado de cinco segundos para indicar que el proceso de cierre ya no debería ser estable.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Cuando se solicita la cancelación en el token:When cancellation is requested on the token:
- Se deben anular las operaciones restantes en segundo plano que realiza la aplicación.Any remaining background operations that the app is performing should be aborted.
- Los métodos llamados en
StopAsync
deberían devolver contenido al momento.Any methods called inStopAsync
should return promptly.
No obstante, las tareas no se abandonan después de solicitar la cancelación, sino que el autor de la llamada espera a que se completen todas las tareas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.
Si la aplicación se cierra inesperadamente (por ejemplo, porque se produzca un error en el proceso de la aplicación), puede que no sea posible llamar a
StopAsync
.If the app shuts down unexpectedly (for example, the app's process fails),StopAsync
might not be called. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo enStopAsync
podrían no producirse.Therefore, any methods called or operations conducted inStopAsync
might not occur.Para ampliar el tiempo de espera predeterminado de apagado de 5 segundos, establezca:To extend the default five second shutdown timeout, set:
- ShutdownTimeout cuando se usa el host genérico.ShutdownTimeout when using Generic Host. Para obtener más información, vea Host genérico de .NET en ASP.NET Core.For more information, see Host genérico de .NET en ASP.NET Core.
- Configuración de los valores de host de tiempo de espera de apagado cuando se usa el host web.Shutdown timeout host configuration setting when using Web Host. Para obtener más información, vea Host web de ASP.NET Core.For more information, see Host web de ASP.NET Core.
El servicio hospedado se activa una vez al inicio de la aplicación y se cierra de manera estable cuando dicha aplicación se cierra.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Si se produce un error durante la ejecución de una tarea en segundo plano, hay que llamar a Dispose
, aun cuando no se haya llamado a StopAsync
.If an error is thrown during background task execution, Dispose
should be called even if StopAsync
isn't called.
Clase base BackgroundServiceBackgroundService base class
BackgroundService es una clase base para implementar un IHostedService de larga duración.BackgroundService is a base class for implementing a long running IHostedService.
Se llama a ExecuteAsync(CancellationToken) para ejecutar el servicio en segundo plano.ExecuteAsync(CancellationToken) is called to run the background service. La implementación devuelve Task, que representa toda la duración del servicio en segundo plano.The implementation returns a Task that represents the entire lifetime of the background service. No se inicia ningún servicio hasta que ExecuteAsync se convierte en asincrónico, mediante una llamada a await
.No further services are started until ExecuteAsync becomes asynchronous, such as by calling await
. Evite realizar un trabajo de inicialización de bloqueo prolongado en ExecuteAsync
.Avoid performing long, blocking initialization work in ExecuteAsync
. El host se bloquea en StopAsync(CancellationToken) a la espera de que ExecuteAsync
se complete.The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync
to complete.
El token de cancelación se desencadena cuando se llama a IHostedService.StopAsync.The cancellation token is triggered when IHostedService.StopAsync is called. La implementación de ExecuteAsync
debe finalizar rápidamente cuando se active el token de cancelación para cerrar correctamente el servicio.Your implementation of ExecuteAsync
should finish promptly when the cancellation token is fired in order to gracefully shut down the service. De lo contrario, el servicio se cierra de manera abrupta durante el tiempo de expiración del cierre.Otherwise, the service ungracefully shuts down at the shutdown timeout. Para más información, consulte la sección sobre la interfaz IHostedService.For more information, see the IHostedService interface section.
Tareas en segundo plano temporizadasTimed background tasks
Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. El temporizador activa el método DoWork
de la tarea.The timer triggers the task's DoWork
method. El temporizador está deshabilitado en StopAsync
y se desecha cuando el contenedor de servicios se elimina en Dispose
:The timer is disabled on StopAsync
and disposed when the service container is disposed on Dispose
:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Timer no espera a que finalicen las ejecuciones anteriores de DoWork
, por lo que es posible que el enfoque mostrado no sea adecuado para todos los escenarios.The Timer doesn't wait for previous executions of DoWork
to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment se usa para incrementar el contador de ejecución como una operación atómica, lo que garantiza que varios subprocesos no actualicen executionCount
simultáneamente.Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount
concurrently.
El servicio se registra en IHostBuilder.ConfigureServices
(Program.cs) con el método de extensión AddHostedService
:The service is registered in IHostBuilder.ConfigureServices
(Program.cs) with the AddHostedService
extension method:
services.AddHostedService<TimedHostedService>();
Consumir un servicio con ámbito en una tarea en segundo planoConsuming a scoped service in a background task
Para usar servicios con ámbito dentro de un BackgroundService, cree un ámbito.To use scoped services within a BackgroundService, create a scope. No se crean ámbitos de forma predeterminada para los servicios hospedados.No scope is created for a hosted service by default.
El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano.The scoped background task service contains the background task's logic. En el ejemplo siguiente:In the following example:
- El servicio es asincrónico.The service is asynchronous. El método
DoWork
devuelve un objetoTask
.TheDoWork
method returns aTask
. Para fines de demostración, se espera un retraso de diez segundos en el métodoDoWork
.For demonstration purposes, a delay of ten seconds is awaited in theDoWork
method. - ILogger se inserta en el servicio.An ILogger is injected into the service.
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
El servicio hospedado crea un ámbito con el fin de resolver el servicio de tareas en segundo plano con ámbito para llamar a su método DoWork
.The hosted service creates a scope to resolve the scoped background task service to call its DoWork
method. DoWork
devuelve Task
, que se espera en ExecuteAsync
:DoWork
returns a Task
, which is awaited in ExecuteAsync
:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Los servicios se registran en IHostBuilder.ConfigureServices
(Program.cs).The services are registered in IHostBuilder.ConfigureServices
(Program.cs). El servicio hospedado se registra en con el método de extensión AddHostedService
:The hosted service is registered with the AddHostedService
extension method:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tareas en segundo plano en colaQueued background tasks
Una cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x:A background task queue is based on the .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
En el ejemplo QueueHostedService
siguiente:In the following QueueHostedService
example:
- El método
BackgroundProcessing
devuelveTask
, que se espera enExecuteAsync
.TheBackgroundProcessing
method returns aTask
, which is awaited inExecuteAsync
. - Las tareas en segundo plano que están en cola se quitan de ella y se ejecutan en
BackgroundProcessing
.Background tasks in the queue are dequeued and executed inBackgroundProcessing
. - Se esperan elementos de trabajo antes de que el servicio se detenga en
StopAsync
.Work items are awaited before the service stops inStopAsync
.
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Un servicio MonitorLoop
controla las tareas de puesta en cola para el servicio hospedado cada vez que se selecciona la tecla w
en un dispositivo de entrada:A MonitorLoop
service handles enqueuing tasks for the hosted service whenever the w
key is selected on an input device:
IBackgroundTaskQueue
se inserta en el servicioMonitorLoop
.TheIBackgroundTaskQueue
is injected into theMonitorLoop
service.- Se llama a
IBackgroundTaskQueue.QueueBackgroundWorkItem
para poner en cola el elemento de trabajo.IBackgroundTaskQueue.QueueBackgroundWorkItem
is called to enqueue a work item. - El elemento de trabajo simula una tarea en segundo plano de larga duración:The work item simulates a long-running background task:
- Se ejecutan tres retrasos de 5 segundos (
Task.Delay
).Three 5-second delays are executed (Task.Delay
). - Una instrucción
try-catch
captura OperationCanceledException si se cancela la tarea.Atry-catch
statement traps OperationCanceledException if the task is cancelled.
- Se ejecutan tres retrasos de 5 segundos (
public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("Monitor Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(() => Monitor());
}
public void Monitor()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
_taskQueue.QueueBackgroundWorkItem(async token =>
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation(
"Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation(
"Queued Background Task {Guid} is running. " +
"{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation(
"Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation(
"Queued Background Task {Guid} was cancelled.", guid);
}
});
}
}
}
}
Los servicios se registran en IHostBuilder.ConfigureServices
(Program.cs).The services are registered in IHostBuilder.ConfigureServices
(Program.cs). El servicio hospedado se registra en con el método de extensión AddHostedService
:The hosted service is registered with the AddHostedService
extension method:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
MonitorLoop
se inicia en Program.Main
:MonitorLoop
is started in Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. En este tema se incluyen tres ejemplos de servicio hospedado:This topic provides three hosted service examples:
- Una tarea en segundo plano que se ejecuta según un temporizador.Background task that runs on a timer.
- Un servicio hospedado que activa un servicio con ámbito.Hosted service that activates a scoped service. El servicio con ámbito puede usar la inserción de dependencias (DI).The scoped service can use dependency injection (DI)
- Tareas en segundo plano en cola que se ejecutan en secuencia.Queued background tasks that run sequentially.
Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)
PackagePackage
Haga referencia al metapaquete Microsoft.AspNetCore.App o agregue una referencia de paquete al paquete Microsoft.Extensions.Hosting.Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.
Interfaz IHostedServiceIHostedService interface
Los servicios hospedados implementan la interfaz IHostedService.Hosted services implement the IHostedService interface. Esta interfaz define dos métodos para los objetos administrados por el host:The interface defines two methods for objects that are managed by the host:
StartAsync(CancellationToken):
StartAsync
contiene la lógica para iniciar la tarea en segundo plano.StartAsync(CancellationToken):StartAsync
contains the logic to start the background task. Al utilizar el host web, se llama aStartAsync
después de que el servidor se haya iniciado y se haya activado IApplicationLifetime.ApplicationStarted.When using the Web Host,StartAsync
is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Al utilizar el host genérico, se llama aStartAsync
antes de que se desencadeneApplicationStarted
.When using the Generic Host,StartAsync
is called beforeApplicationStarted
is triggered.StopAsync(CancellationToken): se desencadena cuando el host está realizando un cierre estable.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown.
StopAsync
contiene la lógica para finalizar la tarea en segundo plano.StopAsync
contains the logic to end the background task. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.El token de cancelación tiene un tiempo de espera predeterminado de cinco segundos para indicar que el proceso de cierre ya no debería ser estable.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Cuando se solicita la cancelación en el token:When cancellation is requested on the token:
- Se deben anular las operaciones restantes en segundo plano que realiza la aplicación.Any remaining background operations that the app is performing should be aborted.
- Los métodos llamados en
StopAsync
deberían devolver contenido al momento.Any methods called inStopAsync
should return promptly.
No obstante, las tareas no se abandonan después de solicitar la cancelación, sino que el autor de la llamada espera a que se completen todas las tareas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.
Si la aplicación se cierra inesperadamente (por ejemplo, porque se produzca un error en el proceso de la aplicación), puede que no sea posible llamar a
StopAsync
.If the app shuts down unexpectedly (for example, the app's process fails),StopAsync
might not be called. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo enStopAsync
podrían no producirse.Therefore, any methods called or operations conducted inStopAsync
might not occur.Para ampliar el tiempo de espera predeterminado de apagado de 5 segundos, establezca:To extend the default five second shutdown timeout, set:
- ShutdownTimeout cuando se usa el host genérico.ShutdownTimeout when using Generic Host. Para obtener más información, vea Host genérico de .NET en ASP.NET Core.For more information, see Host genérico de .NET en ASP.NET Core.
- Configuración de los valores de host de tiempo de espera de apagado cuando se usa el host web.Shutdown timeout host configuration setting when using Web Host. Para obtener más información, vea Host web de ASP.NET Core.For more information, see Host web de ASP.NET Core.
El servicio hospedado se activa una vez al inicio de la aplicación y se cierra de manera estable cuando dicha aplicación se cierra.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Si se produce un error durante la ejecución de una tarea en segundo plano, hay que llamar a Dispose
, aun cuando no se haya llamado a StopAsync
.If an error is thrown during background task execution, Dispose
should be called even if StopAsync
isn't called.
Tareas en segundo plano temporizadasTimed background tasks
Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. El temporizador activa el método DoWork
de la tarea.The timer triggers the task's DoWork
method. El temporizador está deshabilitado en StopAsync
y se desecha cuando el contenedor de servicios se elimina en Dispose
:The timer is disabled on StopAsync
and disposed when the service container is disposed on Dispose
:
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Timer no espera a que finalicen las ejecuciones anteriores de DoWork
, por lo que es posible que el enfoque mostrado no sea adecuado para todos los escenarios.The Timer doesn't wait for previous executions of DoWork
to finish, so the approach shown might not be suitable for every scenario.
El servicio se registra en Startup.ConfigureServices
con el método de extensión AddHostedService
:The service is registered in Startup.ConfigureServices
with the AddHostedService
extension method:
services.AddHostedService<TimedHostedService>();
Consumir un servicio con ámbito en una tarea en segundo planoConsuming a scoped service in a background task
Para usar servicios con ámbito en un elemento IHostedService
, cree un ámbito.To use scoped services within an IHostedService
, create a scope. No se crean ámbitos de forma predeterminada para los servicios hospedados.No scope is created for a hosted service by default.
El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano.The scoped background task service contains the background task's logic. En el siguiente ejemplo, ILogger se inserta en el servicio:In the following example, an ILogger is injected into the service:
internal interface IScopedProcessingService
{
void DoWork();
}
internal class ScopedProcessingService : IScopedProcessingService
{
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("Scoped Processing Service is working.");
}
}
El servicio hospedado crea un ámbito con objeto de resolver el servicio de tareas en segundo plano con ámbito para llamar a su método DoWork
:The hosted service creates a scope to resolve the scoped background task service to call its DoWork
method:
internal class ConsumeScopedServiceHostedService : IHostedService
{
private readonly ILogger _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
Los servicios se registran en Startup.ConfigureServices
.The services are registered in Startup.ConfigureServices
. La implementación IHostedService
se registra con el método de extensión AddHostedService
:The IHostedService
implementation is registered with the AddHostedService
extension method:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tareas en segundo plano en colaQueued background tasks
La cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET Framework 4.x (está previsto que se integre en ASP.NET Core):A background task queue is based on the .NET Framework 4.x QueueBackgroundWorkItem (tentatively scheduled to be built-in for ASP.NET Core):
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
En QueueHostedService
, las tareas en segundo plano en la cola se quitan de la cola y se ejecutan como un servicio BackgroundService, que es una clase base para implementar IHostedService
de ejecución prolongada:In QueueHostedService
, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService
:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
Los servicios se registran en Startup.ConfigureServices
.The services are registered in Startup.ConfigureServices
. La implementación IHostedService
se registra con el método de extensión AddHostedService
:The IHostedService
implementation is registered with the AddHostedService
extension method:
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
En la clase de modelo de página de índice:In the Index page model class:
- Se inserta
IBackgroundTaskQueue
en el constructor y se asigna aQueue
.TheIBackgroundTaskQueue
is injected into the constructor and assigned toQueue
. - Se inserta IServiceScopeFactory y se asigna a
_serviceScopeFactory
.An IServiceScopeFactory is injected and assigned to_serviceScopeFactory
. Se usa el generador se para crear instancias de IServiceScope, que se usa para crear servicios dentro de un ámbito.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Se crea un ámbito para poder usar el elementoAppDbContext
de la aplicación (un servicio con ámbito) para escribir registros de base de datos enIBackgroundTaskQueue
(un servicio de singleton).A scope is created in order to use the app'sAppDbContext
(a scoped service) to write database records in theIBackgroundTaskQueue
(a singleton service).
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
private readonly ILogger _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
public IndexModel(AppDbContext db, IBackgroundTaskQueue queue,
ILogger<IndexModel> logger, IServiceScopeFactory serviceScopeFactory)
{
_db = db;
_logger = logger;
Queue = queue;
_serviceScopeFactory = serviceScopeFactory;
}
public IBackgroundTaskQueue Queue { get; }
Cuando se hace clic en el botón Agregar tarea en la página de índice, se ejecuta el método OnPostAddTask
.When the Add Task button is selected on the Index page, the OnPostAddTask
method is executed. Se llama a QueueBackgroundWorkItem
para poner en cola un elemento de trabajo:QueueBackgroundWorkItem
is called to enqueue a work item:
public IActionResult OnPostAddTaskAsync()
{
Queue.QueueBackgroundWorkItem(async token =>
{
var guid = Guid.NewGuid().ToString();
using (var scope = _serviceScopeFactory.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<AppDbContext>();
for (int delayLoop = 1; delayLoop < 4; delayLoop++)
{
try
{
db.Messages.Add(
new Message()
{
Text = $"Queued Background Task {guid} has " +
$"written a step. {delayLoop}/3"
});
await db.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex,
"An error occurred writing to the " +
"database. Error: {Message}", ex.Message);
}
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
_logger.LogInformation(
"Queued Background Task {Guid} is complete. 3/3", guid);
});
return RedirectToPage();
}
Recursos adicionalesAdditional resources
- Implementar tareas en segundo plano en microservicios con IHostedService y la clase BackgroundServiceImplement background tasks in microservices with IHostedService and the BackgroundService class
- Ejecución de tareas en segundo plano con WebJobs en Azure App ServiceRun background tasks with WebJobs in Azure App Service
- Timer