Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Ein Warteschlangendienst ist ein hervorragendes Beispiel für einen lang laufenden Dienst, bei dem Arbeitsaufgaben in die Warteschlange gestellt und der Reihe nach bearbeitet werden können, sobald vorherige Arbeitsaufgaben abgeschlossen sind. Auf der Vorlage "Worker Service" basierend, bauen Sie auf der Grundlage von BackgroundService neue Funktionalitäten aus.
In diesem Tutorial erfahren Sie, wie:
- Erstellen Sie einen Warteschlangendienst.
- Delegieren Sie Arbeit in eine Aufgabenwarteschlange.
- Registrieren Sie einen Konsolentastenlistener für IHostApplicationLifetime Ereignisse.
Tipp
Der Quellcode "Workers in .NET" ist im Beispielbrowser zum Herunterladen verfügbar. Weitere Informationen finden Sie unter Durchsuchen von Codebeispielen: Worker in .NET.
Voraussetzungen
- .NET 8.0 SDK oder höher
- Eine integrierte .NET-Entwicklungsumgebung (IDE)
- Sie können Visual Studio verwenden
Erstellen eines neuen Projekts
Um ein neues Worker Service-Projekt mit Visual Studio zu erstellen, wählen Sie „Datei“>„Neu“>„Projekt…“ aus. Im Dialogfeld „Neues Projekt erstellen“ suchen Sie nach „Worker Service“ und wählen die „Worker Service“-Vorlage aus. Wenn Sie lieber die .NET CLI verwenden möchten, öffnen Sie Ihr bevorzugtes Terminal in einem Arbeitsverzeichnis. Führen Sie den dotnet new Befehl aus, und ersetzen Sie <Project.Name> durch den gewünschten Projektnamen.
dotnet new worker --name <Project.Name>
Weitere Informationen zum .NET CLI-Projektbefehl für den neuen Workerdienst finden Sie unter dotnet new worker.
Tipp
Wenn Sie Visual Studio Code verwenden, können Sie .NET CLI-Befehle über das integrierte Terminal ausführen. Weitere Informationen finden Sie unter Visual Studio Code: Integrated Terminal.
Warteschlangendienste erstellen
Möglicherweise sind Sie mit der QueueBackgroundWorkItem(Func<CancellationToken,Task>) Funktionalität aus dem System.Web.Hosting Namespace vertraut.
Tipp
Die Funktionalität des System.Web Namespace wurde absichtlich nicht zu .NET portiert und bleibt exklusiv für .NET Framework. Weitere Informationen finden Sie unter Einführung in die inkrementelle Migration von ASP.NET zu ASP.NET Core.
Wenn Sie in .NET einen Dienst modellieren möchten, der von der QueueBackgroundWorkItem Funktionalität inspiriert ist, fügen Sie zunächst eine IBackgroundTaskQueue Schnittstelle zum Projekt hinzu:
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Es gibt zwei Methoden: eine, die Warteschlangenfunktionen verfügbar macht, und eine andere, die zuvor in die Warteschlange eingereihte Arbeitsaufgaben entfernt. Eine Arbeitsaufgabe ist ein Func<CancellationToken, ValueTask>. Fügen Sie als Nächstes die Standardimplementierung zum Projekt hinzu.
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;
}
}
Die vorangehende Implementierung basiert auf einer Channel<T> Warteschlange. Dies BoundedChannelOptions(Int32) wird mit einer expliziten Kapazität aufgerufen. Die Kapazität sollte basierend auf der erwarteten Anwendungslast und der Anzahl der gleichzeitigen Threads festgelegt werden, die auf die Warteschlange zugreifen. BoundedChannelFullMode.Wait bewirkt, dass Aufrufe ChannelWriter<T>.WriteAsync eine Aufgabe zurückgeben, die nur abgeschlossen wird, wenn Speicherplatz verfügbar wird. Dies führt zu Backpressure, wenn sich zu viele Veröffentlicher/Aufrufe anhäufen.
Schreibe die Worker-Klasse um
Im folgenden Beispiel für QueueHostedService gilt:
- Die
ProcessTaskQueueAsync-Methode gibt ein Task inExecuteAsynczurück. - Hintergrundtasks in der Warteschlange werden aus dieser entfernt und in
ProcessTaskQueueAsyncausgeführt. - Auf Arbeitselemente wird gewartet, bevor der Dienst in
StopAsyncangehalten wird.
Ersetzen Sie die vorhandene Worker Klasse durch den folgenden C#-Code, und benennen Sie die Datei in QueueHostedService.cs um.
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);
}
}
Ein MonitorLoop-Dienst verarbeitet das Einreihen von Tasks in die Warteschlange für den gehosteten Dienst, wenn der w-Schlüssel auf einem Eingabegerät ausgewählt wird:
- Die
IBackgroundTaskQueuewird in denMonitorLoop-Dienst eingefügt. -
IBackgroundTaskQueue.QueueBackgroundWorkItemAsyncwird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen. - Das Arbeitselement simuliert eine Hintergrundaufgabe mit langer Ausführungszeit:
- Drei Verzögerungen von jeweils 5 Sekunden werden ausgeführt Delay.
- Eine
try-catch-Anweisung fängt OperationCanceledException ab, wenn die Aufgabe abgebrochen wird.
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);
}
}
}
Ersetzen Sie den vorhandenen Program Inhalt durch den folgenden 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();
Die Dienste werden in (Program.cs) registriert. Der gehostete Dienst wird bei der AddHostedService Erweiterungsmethode registriert.
MonitorLoop wird in der Program.cs-Anweisung der obersten Ebene gestartet:
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Weitere Informationen zum Registrieren von Diensten finden Sie unter Dependency Injection in .NET.
Überprüfen der Dienstfunktionalität
Um die Anwendung in Visual Studio auszuführen, wählen Sie F5 aus, oder wählen Sie die Menüoption " Debuggen>starten" aus. Wenn Sie die .NET CLI verwenden, führen Sie den dotnet run Befehl aus dem Arbeitsverzeichnis aus:
dotnet run
Weitere Informationen zum .NET CLI-Ausführungsbefehl finden Sie unter dotnet run.
Wenn Sie dazu aufgefordert werden, geben Sie mindestens einmal w (oder W) ein, um eine emulierte Arbeitsaufgabe in die Warteschlange einzureihen, wie in der Beispielausgabe gezeigt.
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.
Wenn Sie die Anwendung in Visual Studio ausführen, wählen Sie "Debuggen>beenden" aus. Alternativ können Sie imKonsolenfenster STRG + C auswählen, um den Abbruch zu signalisieren.