Share via


Skapa en kötjänst

En kötjänst är ett bra exempel på en långvarig tjänst, där arbetsobjekt kan placeras i kö och arbetas sekventiellt när tidigare arbetsobjekt har slutförts. Om du förlitar dig på arbetstjänstmallen skapar du nya funktioner ovanpå BackgroundService.

I den här självstudien lär du dig att:

  • Skapa en kötjänst.
  • Delegera arbete till en uppgiftskö.
  • Registrera en nyckellyssnare för konsolen från IHostApplicationLifetime händelser.

Dricks

Alla exempelkällkoden "Arbetare i .NET" finns i exempelwebbläsaren för nedladdning. Mer information finns i Bläddra bland kodexempel: Arbetare i .NET.

Förutsättningar

Skapa ett nytt projekt

Om du vill skapa ett nytt Worker Service-projekt med Visual Studio väljer du Nytt>>filprojekt....I dialogrutan Skapa ett nytt projekt söker du efter "Arbetstjänst" och väljer Mall för Arbetstjänst. Om du hellre vill använda .NET CLI öppnar du din favoritterminal i en arbetskatalog. dotnet new Kör kommandot och ersätt <Project.Name> med önskat projektnamn.

dotnet new worker --name <Project.Name>

Mer information om kommandot .NET CLI new worker service project finns i dotnet new worker( dotnet new worker).

Dricks

Om du använder Visual Studio Code kan du köra .NET CLI-kommandon från den integrerade terminalen. Mer information finns i Visual Studio Code: Integrerad terminal.

Skapa kötjänster

Du kanske känner till QueueBackgroundWorkItem(Func<CancellationToken,Task>) funktionerna från System.Web.Hosting namnområdet.

Dricks

Funktionen för System.Web namnområdet portades avsiktligt inte över till .NET och är exklusiv för .NET Framework. Mer information finns i Kom igång med inkrementell ASP.NET till ASP.NET Core-migrering.

Om du vill modellera en tjänst som är inspirerad av QueueBackgroundWorkItem funktionerna i .NET börjar du med att lägga till ett IBackgroundTaskQueue gränssnitt i projektet:

namespace App.QueueService;

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem);

    ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken);
}

Det finns två metoder, en som exponerar köfunktioner och en annan som rensar tidigare köade arbetsobjekt. Ett arbetsobjekt är en Func<CancellationToken, ValueTask>. Lägg sedan till standardimplementeringen i projektet.

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;
    }
}

Den föregående implementeringen förlitar sig på en Channel<T> som en kö. BoundedChannelOptions(Int32) Anropas med en explicit kapacitet. Kapaciteten ska anges baserat på den förväntade programbelastningen och antalet samtidiga trådar som kommer åt kön. BoundedChannelFullMode.Wait gör att anrop till ChannelWriter<T>.WriteAsync returnerar en uppgift som bara slutförs när utrymmet blir tillgängligt. Vilket leder till backpressure, om för många utgivare/anrop börjar ackumuleras.

Skriv om klassen Worker

I följande QueueHostedService exempel:

  • Metoden ProcessTaskQueueAsync returnerar en Task i ExecuteAsync.
  • Bakgrundsuppgifter i kön tas bort och körs i ProcessTaskQueueAsync.
  • Arbetsobjekt väntar innan tjänsten stoppas i StopAsync.

Ersätt den befintliga Worker klassen med följande C#-kod och byt namn på filen till 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);
    }
}

En MonitorLoop tjänst hanterar lagringsuppgifter för den värdbaserade tjänsten när w nyckeln väljs på en indataenhet:

  • IBackgroundTaskQueue Matas in i tjänstenMonitorLoop.
  • IBackgroundTaskQueue.QueueBackgroundWorkItemAsync anropas för att ställa in ett arbetsobjekt.
  • Arbetsobjektet simulerar en långvarig bakgrundsaktivitet:
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);
        }
    }
}

Ersätt det befintliga Program innehållet med följande C#-kod:

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();

Tjänsterna är registrerade i (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden. MonitorLoop startas i Program.cs-instruktion på toppnivå:

MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();

Mer information om hur du registrerar tjänster finns i Beroendeinmatning i .NET.

Verifiera tjänstfunktioner

Om du vill köra programmet från Visual Studio väljer du F5 eller väljer menyalternativet Felsök>startfelsökning. Om du använder .NET CLI kör dotnet run du kommandot från arbetskatalogen:

dotnet run

Mer information om körningskommandot för .NET CLI finns i dotnet run.

När du uppmanas att ange w (eller W) minst en gång för att köa ett emulerat arbetsobjekt, som visas i exempelutdata:

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.

Om du kör programmet inifrån Visual Studio väljer du Felsöka>Sluta felsöka.... Du kan också välja Ctrl + C i konsolfönstret för att signalera annullering.

Se även