Sdílet prostřednictvím


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

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 in ExecuteAsync.
  • Ú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 StopAsyncsluž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 do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItemAsync je volána k vytvoření fronty pracovní položky.
  • Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
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í.

Viz také