Vytvoření služby fronty
Služba fronty je skvělým příkladem dlouhotrvající služby, kde je možné pracovní položky zařadit do fronty a postupně pracovat s tím, jak jsou dokončeny předchozí pracovní položky. Při závislosti na šabloně Služby pracovního procesu vytvoříte nové funkce nad .BackgroundService
V tomto kurzu se naučíte:
- Vytvořte službu fronty.
- Delegujte práci do fronty úkolů.
- Zaregistrujte naslouchací proces konzoly z IHostApplicationLifetime událostí.
Tip
Všechny ukázkové zdrojové kódy Pracovních procesů v .NET jsou k dispozici v prohlížeči ukázek ke stažení. Další informace najdete v tématu Procházení ukázek kódu: Pracovní procesy v .NET.
Požadavky
- Sada .NET 8.0 SDK nebo novější
- Integrované vývojové prostředí .NET (IDE)
- Neváhejte používat Visual Studio
Vytvoření nového projektu
Pokud chcete vytvořit nový projekt Služby pracovního procesu pomocí sady Visual Studio, vyberte Soubor>nový>projekt.... V dialogovém okně Vytvořit nový projekt vyhledejte "Pracovní služba" a vyberte šablonu pracovní služby. Pokud raději použijete .NET CLI, otevřete svůj oblíbený terminál v pracovním adresáři. dotnet new
Spusťte příkaz a nahraďte název požadovaného <Project.Name>
projektu.
dotnet new worker --name <Project.Name>
Další informace o novém příkazu pracovního procesu rozhraní příkazového řádku .NET CLI najdete v tématu dotnet new worker.
Tip
Pokud používáte Visual Studio Code, můžete z integrovaného terminálu spustit příkazy .NET CLI. Další informace naleznete v tématu Visual Studio Code: Integrovaný terminál.
Vytvoření služeb řazení do front
Možná znáte QueueBackgroundWorkItem(Func<CancellationToken,Task>) funkce z System.Web.Hosting
oboru názvů.
Tip
Funkce System.Web
oboru názvů se záměrně nepředávala na .NET a zůstává výhradní pro rozhraní .NET Framework. Další informace najdete v tématu Začínáme s přírůstkovými ASP.NET pro migraci ASP.NET Core.
Pokud chcete v .NET modelovat službu inspirovanou QueueBackgroundWorkItem
funkcí, začněte přidáním IBackgroundTaskQueue
rozhraní do projektu:
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Existují dvě metody, jedna, která zpřístupňuje funkce fronty, a další, která odřadí dříve zařazené pracovní položky do fronty. Pracovní položka je .Func<CancellationToken, ValueTask>
Dále přidejte do projektu výchozí implementaci.
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;
}
}
Předchozí implementace spoléhá na frontu Channel<T> . Volá se BoundedChannelOptions(Int32) s explicitní kapacitou. Kapacita by měla být nastavená na základě očekávaného zatížení aplikace a počtu souběžných vláken přistupujících k frontě. BoundedChannelFullMode.Wait způsobí, že volání vrátí ChannelWriter<T>.WriteAsync úkol, který se dokončí pouze v případě, že je k dispozici mezera. To vede k zpětnému tlaku, v případě, že příliš mnoho vydavatelů/volání začne nahromadět.
Přepsání třídy Worker
V následujícím QueueHostedService
příkladu:
- Metoda
ProcessTaskQueueAsync
vrátí hodnotu Task inExecuteAsync
. - Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v
ProcessTaskQueueAsync
. - Pracovní položky jsou očekávána před zastavením
StopAsync
služby .
Nahraďte existující Worker
třídu následujícím kódem jazyka C# a přejmenujte soubor na 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);
}
}
MonitorLoop
Služba zpracovává úlohy zařazení do fronty hostované služby při každém w
výběru klíče na vstupním zařízení:
- Vloží se
IBackgroundTaskQueue
doMonitorLoop
služby. IBackgroundTaskQueue.QueueBackgroundWorkItemAsync
je volána k vytvoření fronty pracovní položky.- Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
- Provádí se Delaytři 5sekundová zpoždění .
- Příkaz
try-catch
zachytí OperationCanceledException , pokud je úkol zrušen.
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);
}
}
}
Nahraďte existující Program
obsah následujícím kódem jazyka C#:
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();
Služby jsou zaregistrované v souboru (Program.cs). Hostovaná služba je zaregistrovaná v AddHostedService
metodě rozšíření. MonitorLoop
aplikace is started in Program.cs top-level statement:
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Další informace o registraci služeb naleznete v tématu Injektáž závislostí v .NET.
Ověření funkčnosti služby
Pokud chcete aplikaci spustit ze sady Visual Studio, vyberte F5 nebo vyberte možnost nabídky Ladit>spuštění ladění . Pokud používáte .NET CLI, spusťte dotnet run
příkaz z pracovního adresáře:
dotnet run
Další informace o příkazu pro spuštění .NET CLI najdete v tématu dotnet run.
Po zobrazení výzvy zadejte w
alespoň jednou (nebo W
) do fronty emulovanou pracovní položku, jak je znázorněno v příkladu výstupu:
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.
Pokud aplikaci spouštíte ze sady Visual Studio, vyberte Ladění>zastavit ladění.... Případně můžete výběrem kláves Ctrl + C z okna konzoly signalizovat zrušení.