Eventi
19 nov, 23 - 21 nov, 23
Partecipa alle sessioni online di Microsoft Ignite create per espandere le tue competenze e aiutarti a risolvere i problemi complessi di oggi.
Iscriviti subitoQuesto browser non è più supportato.
Esegui l'aggiornamento a Microsoft Edge per sfruttare i vantaggi di funzionalità più recenti, aggiornamenti della sicurezza e supporto tecnico.
Di Jeow Li ̉
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
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 9 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 9 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:
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:
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.
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync(CancellationToken) contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
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
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:
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:
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.
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 .
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>();
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 :
DoWork
restituisce un oggetto Task
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodo DoWork
.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
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>();
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
:
BackgroundProcessing
metodo restituisce un Task
oggetto , atteso in ExecuteAsync
.BackgroundProcessing
.StopAsync
.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Un 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:
IBackgroundTaskQueue
nel servizio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro.Task.Delay
).try-catch
intercettare OperationCanceledException se l'attività viene annullata.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();
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);
}
}
I modelli del servizio di lavoro supportano .NET native ahead-of-time (AOT) con il --aot
flag :
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>
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:
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:
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.
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync(CancellationToken) contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
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
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:
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:
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.
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 .
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>();
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 :
DoWork
restituisce un oggetto Task
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodo DoWork
.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
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>();
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
:
BackgroundProcessing
metodo restituisce un Task
oggetto , atteso in ExecuteAsync
.BackgroundProcessing
.StopAsync
.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Un 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:
IBackgroundTaskQueue
nel servizio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro.Task.Delay
).try-catch
intercettare OperationCanceledException se l'attività viene annullata.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();
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);
}
}
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:
Visualizzare o scaricare il codice di esempio (procedura per il download)
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:
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.
L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:
StartAsync
contiene la logica per avviare l'attività in background. StartAsync
viene chiamato prima:
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
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:
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:
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.
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.
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>();
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 :
DoWork
restituisce un oggetto Task
. Ai fini della dimostrazione, un ritardo di dieci secondi è atteso nel metodo DoWork
.internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
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>();
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
:
BackgroundProcessing
metodo restituisce un Task
oggetto , atteso in ExecuteAsync
.BackgroundProcessing
.StopAsync
.public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Un 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:
IBackgroundTaskQueue
nel servizio MonitorLoop
.IBackgroundTaskQueue.QueueBackgroundWorkItem
per accodare un elemento di lavoro.Task.Delay
).try-catch
intercettare OperationCanceledException se l'attività viene annullata.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();
Feedback su ASP.NET Core
ASP.NET Core è un progetto di open source. Selezionare un collegamento per fornire feedback:
Eventi
19 nov, 23 - 21 nov, 23
Partecipa alle sessioni online di Microsoft Ignite create per espandere le tue competenze e aiutarti a risolvere i problemi complessi di oggi.
Iscriviti subitoFormazione
Percorso di apprendimento
Creare app e servizi nativi del cloud con .NET e ASP.NET Core - Training
Creare app e servizi indipendenti distribuibili, altamente scalabili e resilienti usando la piattaforma .NET gratuita e open source. Con .NET è possibile usare la tecnologia di microservizi più diffusa, ad esempio Docker, Kubernetes, Dapr, Registro Azure Container e altro ancora per .NET e ASP.NET Servizi e applicazioni Core.