Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Door Jeow Li Huan
Note
Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.
Warning
Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.
Important
Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.
Zie de .NET 9-versie van dit artikel voor de huidige release.
In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:
- Achtergrondtaak die wordt uitgevoerd op een timer.
- Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
- Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.
Worker Service-sjabloon
De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:
<Project Sdk="Microsoft.NET.Sdk.Worker">
De sjabloon gebruiken als basis voor een gehoste services-app:
- Maak een nieuw project.
- Selecteer Worker Service. Kies Volgende.
- Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
- Kies een framework in het dialoogvenster Aanvullende informatie. Klik op Creëren.
Package
Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).
Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.
IHostedService-interface
De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:
StartAsync
StartAsync(CancellationToken) bevat de logica om de achtergrondtaak te starten.
StartAsync wordt eerder aangeroepen:
- De aanvraagverwerkingspijplijn van de app is geconfigureerd.
- De server wordt gestart en IApplicationLifetime.ApplicationStarted wordt geactiveerd.
StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid.
StopAsync
-
StopAsync(CancellationToken) wordt geactiveerd wanneer de host een probleemloos afsluiten uitvoert.
StopAsyncbevat de logica om de achtergrondtaak te beëindigen. Implementeer IDisposable en finalizers (destructors) om onbeheerde resources te verwijderen.
Het annuleringstoken heeft een standaard time-out van 30 seconden om aan te geven dat het afsluitproces niet langer op een zachte wijze mag verlopen. Wanneer annulering wordt aangevraagd op het token:
- Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
- Alle methoden die binnen
StopAsyncworden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.
Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.
Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.
Als u de standaard time-out voor afsluiten van 30 seconden wilt uitbreiden, stelt u het volgende in:
- ShutdownTimeout bij het gebruik van Generic Host. Zie .NET Generic Host in ASP.NET Core voor meer informatie.
- Afsluitinstelling voor time-out wanneer u de Webhost gebruikt. Zie ASP.NET Core-webhost voor meer informatie.
De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.
Basisklasse BackgroundService
BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.
ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.
Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.
Zie de broncode van BackgroundService voor meer informatie.
Getimede achtergrondtaken
Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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 wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario.
Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.
De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.
services.AddHostedService<TimedHostedService>();
Een scoped service gebruiken in een achtergrondtaak
Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.
De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:
- De dienst is asynchroon. De
DoWorkmethode retourneert eenTask. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in deDoWorkmethode verwacht. - Een ILogger wordt in de service geïnjecteerd.
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);
}
}
}
De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen.
DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.
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);
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Achtergrondtaken in wachtrij
Een achtergrondtaakwachtrij is gebaseerd op .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;
}
}
In het volgende QueueHostedService voorbeeld:
- De
BackgroundProcessing-methode geeft eenTaskterug, die wordt afgewacht inExecuteAsync. - Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in
BackgroundProcessing. - Werkitems worden gewacht voordat de service stopt.
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);
}
}
Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.
- De
IBackgroundTaskQueuewordt in deMonitorLoopservice geïnjecteerd. -
IBackgroundTaskQueue.QueueBackgroundWorkItemwordt aangeroepen om een werkitem in de wachtrij te zetten. - Het werkitem simuleert een langlopende achtergrondtaak:
- Er worden drie vertragingen van 5 seconden uitgevoerd (
Task.Delay). - Een
try-catchinstructie vangt OperationCanceledException als de taak wordt geannuleerd.
- Er worden drie vertragingen van 5 seconden uitgevoerd (
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);
}
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
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 wordt gestart in Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchrone getimede achtergrondtaak
Met de volgende code wordt een asynchrone achtergrondtaak met een timer gemaakt.
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);
}
}
Inheemse AOT
De Worker Service-sjablonen ondersteunen .NET native ahead-of-time (AOT) met de --aot flag.
- Maak een nieuw project.
- Selecteer Worker Service. Kies Volgende.
- Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
- In het dialoogvenster Aanvullende informatie :
- Kies een framework.
- Vink het selectievakje Systeemeigen AOT-publicatie inschakelen aan.
- Klik op Creëren.
De AOT-optie voegt <PublishAot>true</PublishAot> toe aan het projectbestand.
<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>
Aanvullende bronnen
In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:
- Achtergrondtaak die wordt uitgevoerd op een timer.
- Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
- Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.
Worker Service-sjabloon
De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:
<Project Sdk="Microsoft.NET.Sdk.Worker">
De sjabloon gebruiken als basis voor een gehoste services-app:
- Maak een nieuw project.
- Selecteer Worker Service. Kies Volgende.
- Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
- Kies een framework in het dialoogvenster Aanvullende informatie. Klik op Creëren.
Package
Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).
Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.
IHostedService-interface
De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:
StartAsync
StartAsync(CancellationToken) bevat de logica om de achtergrondtaak te starten.
StartAsync wordt eerder aangeroepen:
- De aanvraagverwerkingspijplijn van de app is geconfigureerd.
- De server wordt gestart en IApplicationLifetime.ApplicationStarted wordt geactiveerd.
StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid.
StopAsync
-
StopAsync(CancellationToken) wordt geactiveerd wanneer de host een probleemloos afsluiten uitvoert.
StopAsyncbevat de logica om de achtergrondtaak te beëindigen. Implementeer IDisposable en finalizers (destructors) om onbeheerde resources te verwijderen.
Het annuleringstoken heeft een standaard time-out van 30 seconden om aan te geven dat het afsluitproces niet langer op een zachte wijze mag verlopen. Wanneer annulering wordt aangevraagd op het token:
- Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
- Alle methoden die binnen
StopAsyncworden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.
Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.
Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.
Als u de standaard time-out voor afsluiten van 30 seconden wilt uitbreiden, stelt u het volgende in:
- ShutdownTimeout bij het gebruik van Generic Host. Zie .NET Generic Host in ASP.NET Core voor meer informatie.
- Afsluitinstelling voor time-out wanneer u de Webhost gebruikt. Zie ASP.NET Core-webhost voor meer informatie.
De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.
Basisklasse BackgroundService
BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.
ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.
Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.
Zie de broncode van BackgroundService voor meer informatie.
Getimede achtergrondtaken
Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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 wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario.
Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.
De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.
services.AddHostedService<TimedHostedService>();
Een scoped service gebruiken in een achtergrondtaak
Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.
De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:
- De dienst is asynchroon. De
DoWorkmethode retourneert eenTask. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in deDoWorkmethode verwacht. - Een ILogger wordt in de service geïnjecteerd.
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);
}
}
}
De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen.
DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.
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);
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Achtergrondtaken in wachtrij
Een achtergrondtaakwachtrij is gebaseerd op .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;
}
}
In het volgende QueueHostedService voorbeeld:
- De
BackgroundProcessing-methode geeft eenTaskterug, die wordt afgewacht inExecuteAsync. - Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in
BackgroundProcessing. - Werkitems worden gewacht voordat de service stopt.
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);
}
}
Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.
- De
IBackgroundTaskQueuewordt in deMonitorLoopservice geïnjecteerd. -
IBackgroundTaskQueue.QueueBackgroundWorkItemwordt aangeroepen om een werkitem in de wachtrij te zetten. - Het werkitem simuleert een langlopende achtergrondtaak:
- Er worden drie vertragingen van 5 seconden uitgevoerd (
Task.Delay). - Een
try-catchinstructie vangt OperationCanceledException als de taak wordt geannuleerd.
- Er worden drie vertragingen van 5 seconden uitgevoerd (
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);
}
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
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 wordt gestart in Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchrone getimede achtergrondtaak
Met de volgende code wordt een asynchrone achtergrondtaak met een timer gemaakt.
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);
}
}
Aanvullende bronnen
In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:
- Achtergrondtaak die wordt uitgevoerd op een timer.
- Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
- Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.
Voorbeeldcode bekijken of downloaden (hoe download je)
Worker Service-sjabloon
De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:
<Project Sdk="Microsoft.NET.Sdk.Worker">
De sjabloon gebruiken als basis voor een gehoste services-app:
- Maak een nieuw project.
- Selecteer Worker Service. Kies Volgende.
- Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Klik op Creëren.
- Selecteer in het dialoogvenster Een nieuwe werkservice makenmaken.
Package
Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).
Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.
IHostedService-interface
De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:
StartAsync
StartAsync bevat de logica voor het starten van de achtergrondtaak.
StartAsync wordt eerder aangeroepen:
- De aanvraagverwerkingspijplijn van de app is geconfigureerd.
- De server wordt gestart en IApplicationLifetime.ApplicationStarted wordt geactiveerd.
Het standaardgedrag kan worden gewijzigd, zodat de gehoste service StartAsync wordt uitgevoerd nadat de pijplijn van de app is geconfigureerd en ApplicationStarted wordt aangeroepen. Als u het standaardgedrag wilt wijzigen, voegt u de gehoste service (VideosWatcher in het volgende voorbeeld) toe na het aanroepen 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) wordt geactiveerd wanneer de host een probleemloos afsluiten uitvoert.
StopAsyncbevat de logica om de achtergrondtaak te beëindigen. Implementeer IDisposable en finalizers (destructors) om onbeheerde resources te verwijderen.
Het annuleringstoken heeft een standaard time-out van vijf seconden om aan te geven dat het afsluitproces niet langer soepel zal verlopen. Wanneer annulering wordt aangevraagd op het token:
- Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
- Alle methoden die binnen
StopAsyncworden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.
Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.
Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.
Als u de standaard time-out van vijf seconden voor afsluiten wilt verlengen, stelt u het volgende in:
- ShutdownTimeout bij het gebruik van Generic Host. Zie .NET Generic Host in ASP.NET Core voor meer informatie.
- Afsluitinstelling voor time-out wanneer u de Webhost gebruikt. Zie ASP.NET Core-webhost voor meer informatie.
De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.
Basisklasse BackgroundService
BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.
ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.
Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.
StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid. Langlopende taken moeten worden geplaatst in ExecuteAsync. Zie de bron voor BackgroundService voor meer informatie.
Getimede achtergrondtaken
Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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 wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario.
Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.
De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.
services.AddHostedService<TimedHostedService>();
Een scoped service gebruiken in een achtergrondtaak
Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.
De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:
- De dienst is asynchroon. De
DoWorkmethode retourneert eenTask. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in deDoWorkmethode verwacht. - Een ILogger wordt in de service geïnjecteerd.
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);
}
}
}
De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen.
DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.
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);
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Achtergrondtaken in wachtrij
Een achtergrondtaakwachtrij is gebaseerd op .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;
}
}
In het volgende QueueHostedService voorbeeld:
- De
BackgroundProcessing-methode geeft eenTaskterug, die wordt afgewacht inExecuteAsync. - Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in
BackgroundProcessing. - Werkitems worden gewacht voordat de service stopt.
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);
}
}
Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.
- De
IBackgroundTaskQueuewordt in deMonitorLoopservice geïnjecteerd. -
IBackgroundTaskQueue.QueueBackgroundWorkItemwordt aangeroepen om een werkitem in de wachtrij te zetten. - Het werkitem simuleert een langlopende achtergrondtaak:
- Er worden drie vertragingen van 5 seconden uitgevoerd (
Task.Delay). - Een
try-catchinstructie vangt OperationCanceledException als de taak wordt geannuleerd.
- Er worden drie vertragingen van 5 seconden uitgevoerd (
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);
}
}
}
De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:
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 wordt gestart in Program.Main:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();