Attività in background con servizi ospitati in ASP.NET Core
Di Jeow Li ̉
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 8 di questo articolo.
In ASP.NET Core le attività in background possono essere implementate come servizi ospitati. Un servizio ospitato è una classe con logica di attività in background che implementa l'interfaccia IHostedService. Questo articolo fornisce tre esempi di servizi ospitati:
- Attività in background eseguita su un timer.
- Servizio ospitato che attiva un servizio con ambito. Il servizio con ambito può usare l'inserimento delle dipendenze.
- Attività in background in coda che vengono eseguite in sequenza.
Modello di servizio di ruolo di lavoro
Il modello di servizio di ruolo di lavoro di ASP.NET Core rappresenta un punto di partenza per la scrittura di app di servizi a esecuzione prolungata. Un'app creata dal modello del servizio di lavoro specifica l'SDK del ruolo di lavoro nel file di progetto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Per usare il modello come base per un'app di servizi ospitati:
- Creare un nuovo progetto.
- Selezionare Servizio di lavoro. Selezionare Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Selezionare Avanti.
- Nella finestra di dialogo Informazioni aggiuntive scegliere un framework. Seleziona Crea.
Pacchetto
Un'app basata sul modello del servizio di lavoro usa l'SDK Microsoft.NET.Sdk.Worker
e ha un riferimento esplicito al pacchetto Microsoft.Extensions.Hosting . Ad esempio, vedi il file di progetto dell'app di esempio (BackgroundTasksSample.csproj
).
Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, il pacchetto Microsoft.Extensions.Hosting viene fatto riferimento in modo implicito dal framework condiviso. Non è necessario un riferimento esplicito al pacchetto nel file di progetto dell'app.
Interfaccia IHostedService
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync
StartAsync(CancellationToken) contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
- La pipeline di elaborazione delle richieste dell'app è configurata.
- Il server viene avviato e viene attivato IApplicationLifetime.ApplicationStarted .
StartAsync
deve essere limitato alle attività a esecuzione breve perché i servizi ospitati vengono eseguiti in sequenza e non vengono avviati altri servizi fino StartAsync
al completamento.
StopAsync
- StopAsync(CancellationToken) viene attivato quando l'host esegue un arresto normale.
StopAsync
contiene la logica per terminare l'attività in background. Implementare IDisposable e i finalizzatori (distruttori) per eliminare tutte le risorse non gestite.
Il token di annullamento ha un timeout predefinito di 30 secondi per indicare che il processo di arresto non deve più essere normale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi eventuali chiamati in
StopAsync
devono essere completati rapidamente.
Tuttavia, le attività non vengono abbandonate dopo la richiesta di annullamento. Il chiamante attende tuttavia il completamento di tutte le attività.
Se l'app si arresta in modo imprevisto, ad esempio, il processo dell'app ha esito negativo, il metodo StopAsync
potrebbe non essere chiamato. Pertanto è possibile che i metodi chiamati o le operazioni effettuate in StopAsync
non vengano eseguiti.
Per estendere il timeout di arresto predefinito di 30 secondi, impostare:
- ShutdownTimeout quando si usa l'host generico. Per altre informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione dell'host del timeout di arresto quando si usa l'host Web. Per altre informazioni, vedere Host Web ASP.NET Core.
Il servizio ospitato viene attivato una volta all'avvio dell'app e arrestato normalmente all'arresto dell'applicazione. Se viene generato un errore durante l'esecuzione dell'attività in background, deve essere chiamato Dispose
anche se StopAsync
non viene chiamato.
Classe di base BackgroundService
BackgroundService è una classe di base per l'implementazione di un oggetto a esecuzione IHostedServiceprolungata.
ExecuteAsync(CancellationToken) viene chiamato per eseguire il servizio in background. L'implementazione restituisce un oggetto Task che rappresenta l'intera durata del servizio in background. Non vengono avviati altri servizi fino a quando ExecuteAsync non diventa asincrono, ad esempio chiamando await
. Evitare di eseguire operazioni di inizializzazione lunghe e bloccate in ExecuteAsync
. Blocchi host in StopAsync(CancellationToken) in attesa del ExecuteAsync
completamento.
Il token di annullamento viene attivato quando viene chiamato IHostedService.StopAsync . L'implementazione di deve terminare tempestivamente quando viene attivato il token di ExecuteAsync
annullamento per arrestare normalmente il servizio. In caso contrario, il servizio si arresta in modo anomalo al timeout di arresto. Per altre informazioni, vedere la sezione interfaccia IHostedService.
Per altre informazioni, vedere il codice sorgente backgroundservice .
Attività in background programmate
Un'attività programmata in background utilizza la classe System.Threading.Timer. Il timer attiva il metodo DoWork
dell'attività. Il timer viene disabilitato con StopAsync
ed eliminato quando il contenitore dei servizi è eliminato con 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 non attende il completamento delle esecuzioni precedenti di DoWork
, quindi l'approccio illustrato potrebbe non essere adatto per ogni scenario. Interlocked.Increment viene usato per incrementare il contatore di esecuzione come operazione atomica, assicurando che più thread non vengano aggiornati executionCount
contemporaneamente.
Il servizio viene registrato in IHostBuilder.ConfigureServices
(Program.cs
) con il AddHostedService
metodo di estensione:
services.AddHostedService<TimedHostedService>();
Utilizzo di un servizio con ambito in un'attività in background
Per usare i servizi con ambito all'interno di backgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background con ambito contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWork
restituisce un oggettoTask
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork
. - Un ILogger oggetto viene inserito nel servizio.
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);
}
}
}
Il servizio ospitato crea un ambito per risolvere il servizio attività in background con ambito per chiamare il relativo DoWork
metodo. DoWork
restituisce un Task
, atteso 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);
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Attività in background in coda
Una coda di attività in background si basa su .NET 4.x QueueBackgroundWorkItem:
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;
}
}
Nell'esempio seguente QueueHostedService
:
- Il
BackgroundProcessing
metodo restituisce unTask
oggetto , atteso inExecuteAsync
. - Le attività in background nella coda vengono rimosse dalla coda ed eseguite in
BackgroundProcessing
. - Gli elementi di lavoro sono attesi prima che il servizio si arresti in
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 servizio MonitorLoop
gestisce le attività di accodamento per il servizio ospitato ogni volta in cui la chiave w
viene selezionata in un dispositivo di input:
- Viene effettuato l'inserimento di
IBackgroundTaskQueue
nel servizioMonitorLoop
. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro. - L'elemento di lavoro simula un'attività in background a esecuzione prolungata:
- Vengono eseguiti tre ritardi di 5 secondi (
Task.Delay
). - Un'istruzione
try-catch
intercettare OperationCanceledException se l'attività viene annullata.
- Vengono eseguiti tre ritardi di 5 secondi (
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);
}
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
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
viene avviato in Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Attività in background timed asincrona
Il codice seguente crea un'attività in background timed asincrona:
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);
}
}
AOT nativo
I modelli del servizio di lavoro supportano .NET native ahead-of-time (AOT) con il --aot
flag :
- Creare un nuovo progetto.
- Selezionare Servizio di lavoro. Selezionare Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Selezionare Avanti.
- Nella finestra di dialogo Informazioni aggiuntive:
- Scegliere un framework.
- Selezionare la casella di controllo Abilita pubblicazione AOT nativa.
- Seleziona Crea.
L'opzione AOT aggiunge <PublishAot>true</PublishAot>
al file di progetto:
<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>
Risorse aggiuntive
- Unit test dei servizi in background in GitHub.
- Visualizzare o scaricare il codice di esempio (procedura per il download)
- Implementare attività in background in microservizi con IHostedService e la classe BackgroundService
- Eseguire attività in background con Processi Web in Servizio app di Azure
- Timer
In ASP.NET Core le attività in background possono essere implementate come servizi ospitati. Un servizio ospitato è una classe con logica di attività in background che implementa l'interfaccia IHostedService. Questo articolo fornisce tre esempi di servizi ospitati:
- Attività in background eseguita su un timer.
- Servizio ospitato che attiva un servizio con ambito. Il servizio con ambito può usare l'inserimento delle dipendenze.
- Attività in background in coda che vengono eseguite in sequenza.
Modello di servizio di ruolo di lavoro
Il modello di servizio di ruolo di lavoro di ASP.NET Core rappresenta un punto di partenza per la scrittura di app di servizi a esecuzione prolungata. Un'app creata dal modello del servizio di lavoro specifica l'SDK del ruolo di lavoro nel file di progetto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Per usare il modello come base per un'app di servizi ospitati:
- Creare un nuovo progetto.
- Selezionare Servizio di lavoro. Selezionare Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Selezionare Avanti.
- Nella finestra di dialogo Informazioni aggiuntive scegliere un framework. Seleziona Crea.
Pacchetto
Un'app basata sul modello del servizio di lavoro usa l'SDK Microsoft.NET.Sdk.Worker
e ha un riferimento esplicito al pacchetto Microsoft.Extensions.Hosting . Ad esempio, vedi il file di progetto dell'app di esempio (BackgroundTasksSample.csproj
).
Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, il pacchetto Microsoft.Extensions.Hosting viene fatto riferimento in modo implicito dal framework condiviso. Non è necessario un riferimento esplicito al pacchetto nel file di progetto dell'app.
Interfaccia IHostedService
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync
StartAsync(CancellationToken) contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
- La pipeline di elaborazione delle richieste dell'app è configurata.
- Il server viene avviato e viene attivato IApplicationLifetime.ApplicationStarted .
StartAsync
deve essere limitato alle attività a esecuzione breve perché i servizi ospitati vengono eseguiti in sequenza e non vengono avviati altri servizi fino StartAsync
al completamento.
StopAsync
- StopAsync(CancellationToken) viene attivato quando l'host esegue un arresto normale.
StopAsync
contiene la logica per terminare l'attività in background. Implementare IDisposable e i finalizzatori (distruttori) per eliminare tutte le risorse non gestite.
Il token di annullamento ha un timeout predefinito di 30 secondi per indicare che il processo di arresto non deve più essere normale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi eventuali chiamati in
StopAsync
devono essere completati rapidamente.
Tuttavia, le attività non vengono abbandonate dopo la richiesta di annullamento. Il chiamante attende tuttavia il completamento di tutte le attività.
Se l'app si arresta in modo imprevisto, ad esempio, il processo dell'app ha esito negativo, il metodo StopAsync
potrebbe non essere chiamato. Pertanto è possibile che i metodi chiamati o le operazioni effettuate in StopAsync
non vengano eseguiti.
Per estendere il timeout di arresto predefinito di 30 secondi, impostare:
- ShutdownTimeout quando si usa l'host generico. Per altre informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione dell'host del timeout di arresto quando si usa l'host Web. Per altre informazioni, vedere Host Web ASP.NET Core.
Il servizio ospitato viene attivato una volta all'avvio dell'app e arrestato normalmente all'arresto dell'applicazione. Se viene generato un errore durante l'esecuzione dell'attività in background, deve essere chiamato Dispose
anche se StopAsync
non viene chiamato.
Classe di base BackgroundService
BackgroundService è una classe di base per l'implementazione di un oggetto a esecuzione IHostedServiceprolungata.
ExecuteAsync(CancellationToken) viene chiamato per eseguire il servizio in background. L'implementazione restituisce un oggetto Task che rappresenta l'intera durata del servizio in background. Non vengono avviati altri servizi fino a quando ExecuteAsync non diventa asincrono, ad esempio chiamando await
. Evitare di eseguire operazioni di inizializzazione lunghe e bloccate in ExecuteAsync
. Blocchi host in StopAsync(CancellationToken) in attesa del ExecuteAsync
completamento.
Il token di annullamento viene attivato quando viene chiamato IHostedService.StopAsync . L'implementazione di deve terminare tempestivamente quando viene attivato il token di ExecuteAsync
annullamento per arrestare normalmente il servizio. In caso contrario, il servizio si arresta in modo anomalo al timeout di arresto. Per altre informazioni, vedere la sezione interfaccia IHostedService.
Per altre informazioni, vedere il codice sorgente backgroundservice .
Attività in background programmate
Un'attività programmata in background utilizza la classe System.Threading.Timer. Il timer attiva il metodo DoWork
dell'attività. Il timer viene disabilitato con StopAsync
ed eliminato quando il contenitore dei servizi è eliminato con 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 non attende il completamento delle esecuzioni precedenti di DoWork
, quindi l'approccio illustrato potrebbe non essere adatto per ogni scenario. Interlocked.Increment viene usato per incrementare il contatore di esecuzione come operazione atomica, assicurando che più thread non vengano aggiornati executionCount
contemporaneamente.
Il servizio viene registrato in IHostBuilder.ConfigureServices
(Program.cs
) con il AddHostedService
metodo di estensione:
services.AddHostedService<TimedHostedService>();
Utilizzo di un servizio con ambito in un'attività in background
Per usare i servizi con ambito all'interno di backgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background con ambito contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWork
restituisce un oggettoTask
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork
. - Un ILogger oggetto viene inserito nel servizio.
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);
}
}
}
Il servizio ospitato crea un ambito per risolvere il servizio attività in background con ambito per chiamare il relativo DoWork
metodo. DoWork
restituisce un Task
, atteso 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);
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Attività in background in coda
Una coda di attività in background si basa su .NET 4.x QueueBackgroundWorkItem:
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;
}
}
Nell'esempio seguente QueueHostedService
:
- Il
BackgroundProcessing
metodo restituisce unTask
oggetto , atteso inExecuteAsync
. - Le attività in background nella coda vengono rimosse dalla coda ed eseguite in
BackgroundProcessing
. - Gli elementi di lavoro sono attesi prima che il servizio si arresti in
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 servizio MonitorLoop
gestisce le attività di accodamento per il servizio ospitato ogni volta in cui la chiave w
viene selezionata in un dispositivo di input:
- Viene effettuato l'inserimento di
IBackgroundTaskQueue
nel servizioMonitorLoop
. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro. - L'elemento di lavoro simula un'attività in background a esecuzione prolungata:
- Vengono eseguiti tre ritardi di 5 secondi (
Task.Delay
). - Un'istruzione
try-catch
intercettare OperationCanceledException se l'attività viene annullata.
- Vengono eseguiti tre ritardi di 5 secondi (
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);
}
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
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
viene avviato in Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Attività in background timed asincrona
Il codice seguente crea un'attività in background timed asincrona:
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);
}
}
Risorse aggiuntive
- Unit test dei servizi in background in GitHub.
- Visualizzare o scaricare il codice di esempio (procedura per il download)
- Implementare attività in background in microservizi con IHostedService e la classe BackgroundService
- Eseguire attività in background con Processi Web in Servizio app di Azure
- Timer
In ASP.NET Core le attività in background possono essere implementate come servizi ospitati. Un servizio ospitato è una classe con logica di attività in background che implementa l'interfaccia IHostedService. Questo articolo fornisce tre esempi di servizi ospitati:
- Attività in background eseguita su un timer.
- Servizio ospitato che attiva un servizio con ambito. Il servizio con ambito può usare l'inserimento delle dipendenze.
- Attività in background in coda che vengono eseguite in sequenza.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Modello di servizio di ruolo di lavoro
Il modello di servizio di ruolo di lavoro di ASP.NET Core rappresenta un punto di partenza per la scrittura di app di servizi a esecuzione prolungata. Un'app creata dal modello del servizio di lavoro specifica l'SDK del ruolo di lavoro nel file di progetto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Per usare il modello come base per un'app di servizi ospitati:
- Creare un nuovo progetto.
- Selezionare Servizio di lavoro. Selezionare Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Seleziona Crea.
- Nella finestra di dialogo Crea un nuovo servizio di lavoro selezionare Crea.
Pacchetto
Un'app basata sul modello del servizio di lavoro usa l'SDK Microsoft.NET.Sdk.Worker
e ha un riferimento esplicito al pacchetto Microsoft.Extensions.Hosting . Ad esempio, vedi il file di progetto dell'app di esempio (BackgroundTasksSample.csproj
).
Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, il pacchetto Microsoft.Extensions.Hosting viene fatto riferimento in modo implicito dal framework condiviso. Non è necessario un riferimento esplicito al pacchetto nel file di progetto dell'app.
Interfaccia IHostedService
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync
StartAsync
contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
- La pipeline di elaborazione delle richieste dell'app è configurata.
- Il server viene avviato e viene attivato IApplicationLifetime.ApplicationStarted .
Il comportamento predefinito può essere modificato in modo che il servizio StartAsync
ospitato venga eseguito dopo la configurazione della pipeline dell'app e ApplicationStarted
venga chiamato. Per modificare il comportamento predefinito, aggiungere il servizio ospitato (VideosWatcher
nell'esempio seguente) dopo aver chiamato 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
- StopAsync(CancellationToken) viene attivato quando l'host esegue un arresto normale.
StopAsync
contiene la logica per terminare l'attività in background. Implementare IDisposable e i finalizzatori (distruttori) per eliminare tutte le risorse non gestite.
Il token di annullamento ha un timeout predefinito di cinque secondi che indica che il processo di arresto non è più normale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi eventuali chiamati in
StopAsync
devono essere completati rapidamente.
Tuttavia, le attività non vengono abbandonate dopo la richiesta di annullamento. Il chiamante attende tuttavia il completamento di tutte le attività.
Se l'app si arresta in modo imprevisto, ad esempio, il processo dell'app ha esito negativo, il metodo StopAsync
potrebbe non essere chiamato. Pertanto è possibile che i metodi chiamati o le operazioni effettuate in StopAsync
non vengano eseguiti.
Per estendere il timeout di arresto predefinito di cinque secondi, impostare:
- ShutdownTimeout quando si usa l'host generico. Per altre informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione dell'host del timeout di arresto quando si usa l'host Web. Per altre informazioni, vedere Host Web ASP.NET Core.
Il servizio ospitato viene attivato una volta all'avvio dell'app e arrestato normalmente all'arresto dell'applicazione. Se viene generato un errore durante l'esecuzione dell'attività in background, deve essere chiamato Dispose
anche se StopAsync
non viene chiamato.
Classe di base BackgroundService
BackgroundService è una classe di base per l'implementazione di un oggetto a esecuzione IHostedServiceprolungata.
ExecuteAsync(CancellationToken) viene chiamato per eseguire il servizio in background. L'implementazione restituisce un oggetto Task che rappresenta l'intera durata del servizio in background. Non vengono avviati altri servizi fino a quando ExecuteAsync non diventa asincrono, ad esempio chiamando await
. Evitare di eseguire operazioni di inizializzazione lunghe e bloccate in ExecuteAsync
. Blocchi host in StopAsync(CancellationToken) in attesa del ExecuteAsync
completamento.
Il token di annullamento viene attivato quando viene chiamato IHostedService.StopAsync . L'implementazione di deve terminare tempestivamente quando viene attivato il token di ExecuteAsync
annullamento per arrestare normalmente il servizio. In caso contrario, il servizio si arresta in modo anomalo al timeout di arresto. Per altre informazioni, vedere la sezione interfaccia IHostedService.
StartAsync
deve essere limitato alle attività a esecuzione breve perché i servizi ospitati vengono eseguiti in sequenza e non vengono avviati altri servizi fino StartAsync
al completamento. Le attività a esecuzione prolungata devono essere inserite in ExecuteAsync
. Per altre informazioni, vedere l'origine in BackgroundService.
Attività in background programmate
Un'attività programmata in background utilizza la classe System.Threading.Timer. Il timer attiva il metodo DoWork
dell'attività. Il timer viene disabilitato con StopAsync
ed eliminato quando il contenitore dei servizi è eliminato con 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 non attende il completamento delle esecuzioni precedenti di DoWork
, quindi l'approccio illustrato potrebbe non essere adatto per ogni scenario. Interlocked.Increment viene usato per incrementare il contatore di esecuzione come operazione atomica, assicurando che più thread non vengano aggiornati executionCount
contemporaneamente.
Il servizio viene registrato in IHostBuilder.ConfigureServices
(Program.cs
) con il AddHostedService
metodo di estensione:
services.AddHostedService<TimedHostedService>();
Utilizzo di un servizio con ambito in un'attività in background
Per usare i servizi con ambito all'interno di backgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background con ambito contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWork
restituisce un oggettoTask
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork
. - Un ILogger oggetto viene inserito nel servizio.
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);
}
}
}
Il servizio ospitato crea un ambito per risolvere il servizio attività in background con ambito per chiamare il relativo DoWork
metodo. DoWork
restituisce un Task
, atteso 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);
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Attività in background in coda
Una coda di attività in background si basa su .NET 4.x QueueBackgroundWorkItem:
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;
}
}
Nell'esempio seguente QueueHostedService
:
- Il
BackgroundProcessing
metodo restituisce unTask
oggetto , atteso inExecuteAsync
. - Le attività in background nella coda vengono rimosse dalla coda ed eseguite in
BackgroundProcessing
. - Gli elementi di lavoro sono attesi prima che il servizio si arresti in
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 servizio MonitorLoop
gestisce le attività di accodamento per il servizio ospitato ogni volta in cui la chiave w
viene selezionata in un dispositivo di input:
- Viene effettuato l'inserimento di
IBackgroundTaskQueue
nel servizioMonitorLoop
. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro. - L'elemento di lavoro simula un'attività in background a esecuzione prolungata:
- Vengono eseguiti tre ritardi di 5 secondi (
Task.Delay
). - Un'istruzione
try-catch
intercettare OperationCanceledException se l'attività viene annullata.
- Vengono eseguiti tre ritardi di 5 secondi (
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);
}
}
}
I servizi vengono registrati in IHostBuilder.ConfigureServices
(Program.cs
). Il servizio ospitato viene registrato con il AddHostedService
metodo di estensione:
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
viene avviato in Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();