Een wachtrijservice maken
Een wachtrijservice is een goed voorbeeld van een langlopende service, waarbij werkitems in de wachtrij kunnen worden geplaatst en sequentieel kunnen worden gewerkt wanneer eerdere werkitems zijn voltooid. Als u afhankelijk bent van de worker-servicesjabloon, bouwt u nieuwe functionaliteit op boven op de BackgroundServicesjabloon.
In deze zelfstudie leert u het volgende:
- Maak een wachtrijservice.
- Werk delegeren aan een taakwachtrij.
- Registreer een consolesleutellistener van IHostApplicationLifetime gebeurtenissen.
Tip
Alle voorbeeldbroncode 'Workers in .NET' is beschikbaar in de voorbeeldenbrowser om te downloaden. Zie Codevoorbeelden bekijken: Workers in .NET voor meer informatie.
Vereisten
- De .NET 8.0 SDK of hoger
- Een .NET Integrated Development Environment (IDE)
Een nieuw project maken
Als u een nieuw Worker Service-project wilt maken met Visual Studio, selecteert u Bestand>nieuw>project.... Zoek in het dialoogvenster Een nieuw project maken naar 'Worker Service' en selecteer de sjabloon Worker Service. Als u liever de .NET CLI gebruikt, opent u uw favoriete terminal in een werkmap. Voer de dotnet new
opdracht uit en vervang de door de <Project.Name>
gewenste projectnaam.
dotnet new worker --name <Project.Name>
Zie dotnet new worker voor meer informatie over de opdracht .NET CLI new worker service project.
Tip
Als u Visual Studio Code gebruikt, kunt u .NET CLI-opdrachten uitvoeren vanuit de geïntegreerde terminal. Zie Visual Studio Code: Integrated Terminal voor meer informatie.
Wachtrijservices maken
Mogelijk bent u bekend met de QueueBackgroundWorkItem(Func<CancellationToken,Task>) functionaliteit van de System.Web.Hosting
naamruimte.
Tip
De functionaliteit van de System.Web
naamruimte is opzettelijk niet overgedragen naar .NET en blijft exclusief voor .NET Framework. Zie Aan de slag met incrementele ASP.NET voor ASP.NET Core-migratie voor meer informatie.
Als u in .NET een service wilt modelleren die is geïnspireerd op de QueueBackgroundWorkItem
functionaliteit, begint u met het toevoegen van een IBackgroundTaskQueue
interface aan het project:
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Er zijn twee methoden, een die wachtrijfunctionaliteit beschikbaar maakt en een andere methode die eerder in de wachtrij geplaatste werkitems dequenteert. Een werkitem is een Func<CancellationToken, ValueTask>
. Voeg vervolgens de standaard implementatie toe aan het project.
using System.Threading.Channels;
namespace App.QueueService;
public sealed class DefaultBackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public DefaultBackgroundTaskQueue(int capacity)
{
BoundedChannelOptions options = new(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
ArgumentNullException.ThrowIfNull(workItem);
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
Func<CancellationToken, ValueTask>? workItem =
await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
De voorgaande implementatie is afhankelijk van een Channel<T> wachtrij. De BoundedChannelOptions(Int32) naam wordt aangeroepen met een expliciete capaciteit. Capaciteit moet worden ingesteld op basis van de verwachte belasting van de toepassing en het aantal gelijktijdige threads die toegang hebben tot de wachtrij. BoundedChannelFullMode.Wait zorgt ervoor dat aanroepen ChannelWriter<T>.WriteAsync een taak retourneren, die alleen wordt voltooid wanneer er ruimte beschikbaar is. Wat leidt tot backpressie, voor het geval er te veel uitgevers/aanroepen beginnen te accumuleren.
De werkrolklasse herschrijven
In het volgende QueueHostedService
voorbeeld:
- De
ProcessTaskQueueAsync
methode retourneert een Task inExecuteAsync
. - Achtergrondtaken in de wachtrij worden ontkend en uitgevoerd in
ProcessTaskQueueAsync
. - Werkitems worden gewacht voordat de service stopt.
StopAsync
Vervang de bestaande Worker
klasse door de volgende C#-code en wijzig de naam van het bestand in QueueHostedService.cs.
namespace App.QueueService;
public sealed class QueuedHostedService(
IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("""
{Name} is running.
Tap W to add a work item to the
background queue.
""",
nameof(QueuedHostedService));
return ProcessTaskQueueAsync(stoppingToken);
}
private async Task ProcessTaskQueueAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
Func<CancellationToken, ValueTask>? workItem =
await taskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
catch (OperationCanceledException)
{
// Prevent throwing if stoppingToken was signaled
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred executing task work item.");
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
$"{nameof(QueuedHostedService)} is stopping.");
await base.StopAsync(stoppingToken);
}
}
Een MonitorLoop
service verwerkt enqueuingtaken voor de gehoste service wanneer de w
sleutel wordt geselecteerd op een invoerapparaat:
- De
IBackgroundTaskQueue
wordt in deMonitorLoop
service geïnjecteerd. IBackgroundTaskQueue.QueueBackgroundWorkItemAsync
wordt aangeroepen om een werkitem in de wachtrij te zetten.- Het werkitem simuleert een langlopende achtergrondtaak:
- Er worden drie vertragingen van 5 seconden uitgevoerd Delay.
- Een
try-catch
instructie trapt OperationCanceledException als de taak wordt geannuleerd.
namespace App.QueueService;
public sealed class MonitorLoop(
IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
private readonly CancellationToken _cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
{
logger.LogInformation($"{nameof(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(BuildWorkItemAsync);
}
}
}
private async ValueTask BuildWorkItemAsync(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid();
logger.LogInformation("Queued work item {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 work item {Guid} is running. {DelayLoop}/3", guid, delayLoop);
}
if (delayLoop is 3)
{
logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Vervang de bestaande Program
inhoud door de volgende C#-code:
using App.QueueService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<MonitorLoop>();
builder.Services.AddHostedService<QueuedHostedService>();
builder.Services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(builder.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new DefaultBackgroundTaskQueue(queueCapacity);
});
IHost host = builder.Build();
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
host.Run();
De services worden geregistreerd in (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService
extensiemethode. MonitorLoop
is gestart in de instructie op het hoogste niveau van Program.cs :
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Zie Afhankelijkheidsinjectie in .NET voor meer informatie over het registreren van services.
Servicefunctionaliteit controleren
Als u de toepassing vanuit Visual Studio wilt uitvoeren, selecteert u F5 of selecteert u de menuoptie Foutopsporing> starten. Als u de .NET CLI gebruikt, voert u de dotnet run
opdracht uit vanuit de werkmap:
dotnet run
Zie dotnet run voor meer informatie over de .NET CLI-run-opdracht.
Wanneer u wordt gevraagd het (of W
) minstens één keer in te voeren om een geëmuleerd werkitem in de w
wachtrij te plaatsen, zoals wordt weergegeven in de voorbeelduitvoer:
info: App.QueueService.MonitorLoop[0]
MonitorAsync loop is starting.
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is running.
Tap W to add a work item to the background queue.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\queue-service
winfo: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is starting.
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 1/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 2/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 3/3
info: App.QueueService.MonitorLoop[0]
Queued Background Task 8453f845-ea4a-4bcb-b26e-c76c0d89303e is complete.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is stopping.
Als u de toepassing vanuit Visual Studio uitvoert, selecteert u Foutopsporing>stoppen.... U kunt ook Ctrl + C selecteren in het consolevenster om annulering te signaleren.