Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Di Jeow Li Huan
Note
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 10 di questo articolo.
Warning
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 10 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:
- Processo in background attivato da un timer.
- Servizio ospitato che attiva un servizio limitato. Il servizio con ambito può usare l'iniezione delle dipendenze (DI).
- Attività in background in coda che vengono eseguite in sequenza.
Modello di servizio Worker
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 di servizio Windows Worker specifica l'SDK di Worker 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. Seleziona Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Seleziona Avanti.
- Nella finestra di dialogo Informazioni aggiuntive scegliere un framework. Fare clic su Crea.
Package
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 a quando StartAsync non è completato.
StopAsync
-
StopAsync(CancellationToken) viene attivato quando l'host esegue un arresto normale.
StopAsynccontiene 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 graduale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi chiamati in
StopAsyncdevono restituire il controllo 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 ulteriori informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione del timeout di spegnimento dell'host quando si utilizza il Web Host. 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 nel pool di thread per eseguire il servizio in background. L'implementazione restituisce un oggetto Task che rappresenta l'intera durata del servizio in background. Blocchi host in StopAsync(CancellationToken) in attesa del ExecuteAsync completamento.
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 ExecuteAsync dovrebbe terminare tempestivamente quando viene attivato il token di annullamento per effettuare una chiusura ordinata del servizio. Altrimenti, il servizio si blocca bruscamente al timeout di arresto. Per altre informazioni, vedere la sezione interfaccia IHostedService.
Per altre informazioni, vedere il codice sorgente backgroundservice .
Attività di 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 delimitato in un'attività in background
Per usare servizi con ambito definito all'interno di un BackgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background limitata contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWorkrestituisce un oggettoTask. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork. - Un ILogger viene iniettato 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 uno scope per risolvere il servizio di attività in background con ambito per chiamare il metodo relativo DoWork.
DoWork restituisce un Task, in attesa di 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 Framework 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
BackgroundProcessingmetodo restituisce unTaskoggetto, atteso inExecuteAsync. - Le attività in background vengono rimosse 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
IBackgroundTaskQueuenel servizioMonitorLoop. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItemper 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-catchintercetta OperationCanceledException quando 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à asincrona in background a tempo
Il codice seguente crea un'attività in background asincrona temporizzata:
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.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
AOT nativo
I modelli di servizio Worker supportano .NET native ahead-of-time (AOT) con il flag --aot :
- Creare un nuovo progetto.
- Selezionare Servizio di lavoro. Seleziona Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Seleziona Avanti.
- Nella finestra di dialogo Informazioni aggiuntive:
- Scegliere un framework.
- Selezionare la casella di controllo Abilita AOT nativa pubblicazione.
- Fare clic su 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
- Test unitari dei servizi in background su GitHub.
- Visualizzare o scaricare il codice di esempio (procedura per il download)
- Implementare attività in background nei microservizi con IHostedService e la classe BackgroundService
- Eseguire attività in background con WebJobs 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:
- Processo in background attivato da un timer.
- Servizio ospitato che attiva un servizio limitato. Il servizio con ambito può usare l'iniezione delle dipendenze (DI).
- Attività in background in coda che vengono eseguite in sequenza.
Modello di servizio Worker
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 di servizio Windows Worker specifica l'SDK di Worker 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. Seleziona Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Seleziona Avanti.
- Nella finestra di dialogo Informazioni aggiuntive scegliere un framework. Fare clic su Crea.
Package
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 a quando StartAsync non è completato.
StopAsync
-
StopAsync(CancellationToken) viene attivato quando l'host esegue un arresto normale.
StopAsynccontiene 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 graduale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi chiamati in
StopAsyncdevono restituire il controllo 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 ulteriori informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione del timeout di spegnimento dell'host quando si utilizza il Web Host. 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 ExecuteAsync dovrebbe terminare tempestivamente quando viene attivato il token di annullamento per effettuare una chiusura ordinata del servizio. Altrimenti, il servizio si blocca bruscamente al timeout di arresto. Per altre informazioni, vedere la sezione interfaccia IHostedService.
Per altre informazioni, vedere il codice sorgente backgroundservice .
Attività di 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 delimitato in un'attività in background
Per usare servizi con ambito definito all'interno di un BackgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background limitata contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWorkrestituisce un oggettoTask. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork. - Un ILogger viene iniettato 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 uno scope per risolvere il servizio di attività in background con ambito per chiamare il metodo relativo DoWork.
DoWork restituisce un Task, in attesa di 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 Framework 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
BackgroundProcessingmetodo restituisce unTaskoggetto, atteso inExecuteAsync. - Le attività in background vengono rimosse 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
IBackgroundTaskQueuenel servizioMonitorLoop. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItemper 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-catchintercetta OperationCanceledException quando 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à asincrona in background a tempo
Il codice seguente crea un'attività in background asincrona temporizzata:
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.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Risorse aggiuntive
- Test unitari dei servizi in background su GitHub.
- Visualizzare o scaricare il codice di esempio (procedura per il download)
- Implementare attività in background nei microservizi con IHostedService e la classe BackgroundService
- Eseguire attività in background con WebJobs 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:
- Processo in background attivato da un timer.
- Servizio ospitato che attiva un servizio limitato. Il servizio con ambito può usare l'iniezione delle dipendenze (DI).
- Attività in background in coda che vengono eseguite in sequenza.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Modello di servizio Worker
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 di servizio Windows Worker specifica l'SDK di Worker 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. Seleziona Avanti.
- Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito. Fare clic su Crea.
- Nella finestra di dialogo Crea un nuovo servizio di lavoro selezionare Crea.
Package
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.
StopAsynccontiene 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 per indicare che il processo di arresto non sarà più graduale. Quando viene richiesto l'annullamento sul token:
- Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.
- Tutti i metodi chiamati in
StopAsyncdevono restituire il controllo 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 ulteriori informazioni, vedere Host generico .NET in ASP.NET Core.
- Impostazione di configurazione del timeout di spegnimento dell'host quando si utilizza il Web Host. 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 ExecuteAsync dovrebbe terminare tempestivamente quando viene attivato il token di annullamento per effettuare una chiusura ordinata del servizio. Altrimenti, il servizio si blocca bruscamente 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 a quando StartAsync non è completato. Le attività a esecuzione prolungata devono essere inserite in ExecuteAsync. Per ulteriori informazioni, vedere la fonte in BackgroundService.
Attività di 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 delimitato in un'attività in background
Per usare servizi con ambito definito all'interno di un BackgroundService, creare un ambito. Non viene creato automaticamente alcun ambito per un servizio ospitato.
Il servizio dell'attività in background limitata contiene la logica dell'attività in background. Nell'esempio seguente :
- Il servizio è asincrono. Il metodo
DoWorkrestituisce un oggettoTask. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodoDoWork. - Un ILogger viene iniettato 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 uno scope per risolvere il servizio di attività in background con ambito per chiamare il metodo relativo DoWork.
DoWork restituisce un Task, in attesa di 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 Framework 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
BackgroundProcessingmetodo restituisce unTaskoggetto, atteso inExecuteAsync. - Le attività in background vengono rimosse 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
IBackgroundTaskQueuenel servizioMonitorLoop. - Viene effettuata la chiamata di
IBackgroundTaskQueue.QueueBackgroundWorkItemper 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-catchintercetta OperationCanceledException quando 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();