Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis informaciónEste explorador xa non é compatible.
Actualice a Microsoft Edge para dispoñer das funcionalidades máis recentes, as actualizacións de seguranza e a asistencia técnica.
Por Jeow Li Huan
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Aviso
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService. En este artículo se incluyen tres ejemplos de servicio hospedado:
La plantilla Worker Service de ASP.NET Core sirve de punto de partida para escribir aplicaciones de servicio de larga duración. Una aplicación creada a partir de la plantilla Worker Service especifica el SDK de trabajo en su archivo del proyecto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar la plantilla como base de una aplicación de servicios hospedados:
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. Por ejemplo, consulte el archivo del proyecto de la aplicación de ejemplo (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. No se requiere una referencia de paquete explícita en el archivo del proyecto de la aplicación.
La interfaz IHostedService define dos métodos para los objetos administrados por el host:
StartAsync(CancellationToken) contiene la lógica para iniciar la tarea en segundo plano. Se llama a StartAsync
antes de que:
StartAsync
debe limitarse a tareas de ejecución corta porque los servicios hospedados se ejecutan secuencialmente, y no se inician más servicios hasta que StartAsync
se ejecuta hasta su finalización.
StopAsync
contiene la lógica para finalizar la tarea en segundo plano. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.El token de cancelación tiene un tiempo de espera predeterminado de 30 segundos para indicar que el proceso de cierre ya no debería ser estable. Cuando se solicita la cancelación en el token:
StopAsync
deberían devolver contenido al momento.Pero 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.
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
. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo en StopAsync
podrían no producirse.
Para ampliar el tiempo de espera predeterminado de apagado de 30 segundos, establezca:
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. 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
.
BackgroundService es una clase base para implementar un IHostedService de larga duración.
Se llama a ExecuteAsync(CancellationToken) para ejecutar el servicio en segundo plano. La implementación devuelve Task, que representa toda la duración del servicio en segundo plano. No se inicia ningún servicio hasta que ExecuteAsync se convierte en asincrónico, mediante una llamada a await
. Evite realizar un trabajo de inicialización de bloqueo prolongado en ExecuteAsync
. El host se bloquea en StopAsync(CancellationToken) a la espera de que ExecuteAsync
se complete.
El token de cancelación se desencadena cuando se llama a IHostedService.StopAsync. La implementación de ExecuteAsync
debe finalizar rápidamente cuando se active el token de cancelación para cerrar correctamente el servicio. De lo contrario, el servicio se cierra de manera abrupta durante el tiempo de expiración del cierre. Para más información, consulte la sección sobre la interfaz IHostedService.
Para obtener más información, vea el código fuente de BackgroundService.
Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer. El temporizador activa el método DoWork
de la tarea. El temporizador está deshabilitado en StopAsync
y se desecha cuando el contenedor de servicios se elimina en Dispose
:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
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. 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.
El servicio se registra en IHostBuilder.ConfigureServices
(Program.cs
) con el método de extensión AddHostedService
:
services.AddHostedService<TimedHostedService>();
Para usar servicios con ámbito dentro de un BackgroundService, cree un ámbito. No se crean ámbitos de forma predeterminada para los servicios hospedados.
El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano. En el ejemplo siguiente:
DoWork
devuelve un objeto Task
. Para fines de demostración, se espera un retraso de diez segundos en el método DoWork
.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
. DoWork
devuelve Task
, que se espera en 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Una cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
En el ejemplo QueueHostedService
siguiente:
BackgroundProcessing
devuelve Task
, que se espera en ExecuteAsync
.BackgroundProcessing
.StopAsync
.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:
IBackgroundTaskQueue
se inserta en el servicio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
para poner en cola el elemento de trabajo.Task.Delay
).try-catch
captura OperationCanceledException si se cancela la tarea.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("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
se inicia en Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
El código siguiente crea una tarea en segundo plano asincrónica temporizada:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Las plantillas de servicio de trabajo admiten .NET ahead-of-time (AOT) nativa con la marca --aot
:
La opción AOT agrega <PublishAot>true</PublishAot>
al archivo del proyecto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
+ <PublishAot>true</PublishAot>
<UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
</ItemGroup>
</Project>
En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService. En este artículo se incluyen tres ejemplos de servicio hospedado:
La plantilla Worker Service de ASP.NET Core sirve de punto de partida para escribir aplicaciones de servicio de larga duración. Una aplicación creada a partir de la plantilla Worker Service especifica el SDK de trabajo en su archivo del proyecto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar la plantilla como base de una aplicación de servicios hospedados:
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. Por ejemplo, consulte el archivo del proyecto de la aplicación de ejemplo (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. No se requiere una referencia de paquete explícita en el archivo del proyecto de la aplicación.
La interfaz IHostedService define dos métodos para los objetos administrados por el host:
StartAsync(CancellationToken) contiene la lógica para iniciar la tarea en segundo plano. Se llama a StartAsync
antes de que:
StartAsync
debe limitarse a tareas de ejecución corta porque los servicios hospedados se ejecutan secuencialmente, y no se inician más servicios hasta que StartAsync
se ejecuta hasta su finalización.
StopAsync
contiene la lógica para finalizar la tarea en segundo plano. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.El token de cancelación tiene un tiempo de espera predeterminado de 30 segundos para indicar que el proceso de cierre ya no debería ser estable. Cuando se solicita la cancelación en el token:
StopAsync
deberían devolver contenido al momento.Pero 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.
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
. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo en StopAsync
podrían no producirse.
Para ampliar el tiempo de espera predeterminado de apagado de 30 segundos, establezca:
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. 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
.
BackgroundService es una clase base para implementar un IHostedService de larga duración.
Se llama a ExecuteAsync(CancellationToken) para ejecutar el servicio en segundo plano. La implementación devuelve Task, que representa toda la duración del servicio en segundo plano. No se inicia ningún servicio hasta que ExecuteAsync se convierte en asincrónico, mediante una llamada a await
. Evite realizar un trabajo de inicialización de bloqueo prolongado en ExecuteAsync
. El host se bloquea en StopAsync(CancellationToken) a la espera de que ExecuteAsync
se complete.
El token de cancelación se desencadena cuando se llama a IHostedService.StopAsync. La implementación de ExecuteAsync
debe finalizar rápidamente cuando se active el token de cancelación para cerrar correctamente el servicio. De lo contrario, el servicio se cierra de manera abrupta durante el tiempo de expiración del cierre. Para más información, consulte la sección sobre la interfaz IHostedService.
Para obtener más información, vea el código fuente de BackgroundService.
Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer. El temporizador activa el método DoWork
de la tarea. El temporizador está deshabilitado en StopAsync
y se desecha cuando el contenedor de servicios se elimina en Dispose
:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
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. 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.
El servicio se registra en IHostBuilder.ConfigureServices
(Program.cs
) con el método de extensión AddHostedService
:
services.AddHostedService<TimedHostedService>();
Para usar servicios con ámbito dentro de un BackgroundService, cree un ámbito. No se crean ámbitos de forma predeterminada para los servicios hospedados.
El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano. En el ejemplo siguiente:
DoWork
devuelve un objeto Task
. Para fines de demostración, se espera un retraso de diez segundos en el método DoWork
.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
. DoWork
devuelve Task
, que se espera en 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Una cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
En el ejemplo QueueHostedService
siguiente:
BackgroundProcessing
devuelve Task
, que se espera en ExecuteAsync
.BackgroundProcessing
.StopAsync
.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:
IBackgroundTaskQueue
se inserta en el servicio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
para poner en cola el elemento de trabajo.Task.Delay
).try-catch
captura OperationCanceledException si se cancela la tarea.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("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
se inicia en Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
El código siguiente crea una tarea en segundo plano asincrónica temporizada:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService. En este artículo se incluyen tres ejemplos de servicio hospedado:
Vea o descargue el código de ejemplo (cómo descargarlo)
La plantilla Worker Service de ASP.NET Core sirve de punto de partida para escribir aplicaciones de servicio de larga duración. Una aplicación creada a partir de la plantilla Worker Service especifica el SDK de trabajo en su archivo del proyecto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar la plantilla como base de una aplicación de servicios hospedados:
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. Por ejemplo, consulte el archivo del proyecto de la aplicación de ejemplo (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. No se requiere una referencia de paquete explícita en el archivo del proyecto de la aplicación.
La interfaz IHostedService define dos métodos para los objetos administrados por el host:
StartAsync
contiene la lógica para iniciar la tarea en segundo plano. Se llama a StartAsync
antes de que:
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 a ApplicationStarted
. Para cambiar el comportamiento predeterminado, agregue el servicio hospedado (VideosWatcher
en el ejemplo siguiente) después de llamar a ConfigureWebHostDefaults
:
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>();
});
}
StopAsync
contiene la lógica para finalizar la tarea en segundo plano. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.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. Cuando se solicita la cancelación en el token:
StopAsync
deberían devolver contenido al momento.Pero 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.
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
. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo en StopAsync
podrían no producirse.
Para ampliar el tiempo de espera predeterminado de apagado de 5 segundos, establezca:
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. 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
.
BackgroundService es una clase base para implementar un IHostedService de larga duración.
Se llama a ExecuteAsync(CancellationToken) para ejecutar el servicio en segundo plano. La implementación devuelve Task, que representa toda la duración del servicio en segundo plano. No se inicia ningún servicio hasta que ExecuteAsync se convierte en asincrónico, mediante una llamada a await
. Evite realizar un trabajo de inicialización de bloqueo prolongado en ExecuteAsync
. El host se bloquea en StopAsync(CancellationToken) a la espera de que ExecuteAsync
se complete.
El token de cancelación se desencadena cuando se llama a IHostedService.StopAsync. La implementación de ExecuteAsync
debe finalizar rápidamente cuando se active el token de cancelación para cerrar correctamente el servicio. De lo contrario, el servicio se cierra de manera abrupta durante el tiempo de expiración del cierre. Para más información, consulte la sección sobre la interfaz IHostedService.
StartAsync
debe limitarse a tareas de ejecución corta porque los servicios hospedados se ejecutan secuencialmente, y no se inician más servicios hasta que StartAsync
se ejecuta hasta su finalización. Las tareas de larga duración deben colocarse en ExecuteAsync
. Para obtener más información, vea el origen para BackgroundService.
Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer. El temporizador activa el método DoWork
de la tarea. El temporizador está deshabilitado en StopAsync
y se desecha cuando el contenedor de servicios se elimina en 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. 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.
El servicio se registra en IHostBuilder.ConfigureServices
(Program.cs
) con el método de extensión AddHostedService
:
services.AddHostedService<TimedHostedService>();
Para usar servicios con ámbito dentro de un BackgroundService, cree un ámbito. No se crean ámbitos de forma predeterminada para los servicios hospedados.
El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano. En el ejemplo siguiente:
DoWork
devuelve un objeto Task
. Para fines de demostración, se espera un retraso de diez segundos en el método DoWork
.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
. DoWork
devuelve Task
, que se espera en 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Una cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
En el ejemplo QueueHostedService
siguiente:
BackgroundProcessing
devuelve Task
, que se espera en ExecuteAsync
.BackgroundProcessing
.StopAsync
.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:
IBackgroundTaskQueue
se inserta en el servicio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
para poner en cola el elemento de trabajo.Task.Delay
).try-catch
captura OperationCanceledException si se cancela la tarea.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("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken 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
). El servicio hospedado se registra en con el método de extensión AddHostedService
:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop
se inicia en Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Comentarios de ASP.NET Core
ASP.NET Core é un proxecto de código aberto. Selecciona unha ligazón para ofrecer comentarios:
Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis información