Share via


Üzenetsor-szolgáltatás létrehozása

Az üzenetsor-szolgáltatás kiváló példa egy hosszú ideig futó szolgáltatásra, ahol a munkaelemek sorba állíthatók és egymás után dolgozhatnak a korábbi munkaelemek befejezésekor. A Worker Service sablonra támaszkodva új funkciókat építhet ki a BackgroundService.

Ebben az oktatóanyagban az alábbiakkal fog megismerkedni:

  • Hozzon létre egy üzenetsor-szolgáltatást.
  • Munka delegálása feladatsorba.
  • Konzolkulcs-figyelő regisztrálása eseményekből IHostApplicationLifetime .

Tipp.

A "Feldolgozók a .NET-ben" példaforráskódok mindegyike letölthető a Mintaböngészőben . További információ: Kódminták tallózása: Feldolgozók a .NET-ben.

Előfeltételek

Új projekt létrehozása

Ha új Worker Service-projektet szeretne létrehozni a Visual Studióval, válassza a Fájl>új>projekt... lehetőséget. Az Új projekt létrehozása párbeszédpanelen keressen rá a "Worker Service" kifejezésre, és válassza a Worker Service sablont. Ha inkább a .NET CLI-t szeretné használni, nyissa meg kedvenc terminálját egy munkakönyvtárban. Futtassa a dotnet new parancsot, és cserélje le a <Project.Name> kívánt projektnevet.

dotnet new worker --name <Project.Name>

A .NET CLI új feldolgozói szolgáltatás projektparancsával kapcsolatos további információkért lásd: dotnet new worker.

Tipp.

Ha Visual Studio Code-ot használ, .NET CLI-parancsokat futtathat az integrált terminálról. További információ: Visual Studio Code: Integrated Terminal.

Üzenetsor-szolgáltatások létrehozása

Lehet, hogy ismeri a QueueBackgroundWorkItem(Func<CancellationToken,Task>) névtér funkcióit System.Web.Hosting .

Tipp.

A névtér funkcióját System.Web szándékosan nem portozták át a .NET-be, és továbbra is kizárólagosan .NET-keretrendszer. További információ: Első lépések a növekményes ASP.NET az alapvető áttelepítés ASP.NET.

A .NET-ben a funkciók által QueueBackgroundWorkItem inspirált szolgáltatás modellezéséhez először adjon hozzá egy IBackgroundTaskQueue felületet a projekthez:

namespace App.QueueService;

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

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

Két módszer létezik, az egyik a sorban állási funkciókat teszi elérhetővé, a másik pedig a korábban várólistára helyezett munkaelemeket. A munkaelem egy Func<CancellationToken, ValueTask>. Ezután adja hozzá az alapértelmezett implementációt a projekthez.

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

Az előző implementáció egy Channel<T> üzenetsorra támaszkodik. A BoundedChannelOptions(Int32) meghívás explicit kapacitással történik. A kapacitást az alkalmazás várt terhelése és az üzenetsorhoz hozzáférő egyidejű szálak száma alapján kell beállítani. BoundedChannelFullMode.Wait a hívások ChannelWriter<T>.WriteAsync egy feladatot adnak vissza, amely csak akkor fejeződik be, ha a terület elérhetővé válik. Ami visszanyomáshoz vezet, ha túl sok közzétevő/hívás kezd halmozódik fel.

A Feldolgozó osztály újraírása

Az alábbi QueueHostedService példában:

  • A ProcessTaskQueueAsync metódus egy in ExecuteAsyncértéket ad Task vissza.
  • Az üzenetsor háttérfeladatai le lesznek kérdezve és végrehajtva a következőben ProcessTaskQueueAsync: .
  • A munkaelemeket a szolgáltatás leállása előtt várjuk.StopAsync

Cserélje le a meglévő Worker osztályt a következő C#-kódra, és nevezze át a fájlt QueueHostedService.cs-re.

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

A MonitorLoop szolgáltatás kezeli az üzemeltetett szolgáltatás lekérdezési feladatait, amikor a w kulcs ki van választva egy bemeneti eszközön:

  • A IBackgroundTaskQueue beszúrt a szolgáltatásba MonitorLoop .
  • IBackgroundTaskQueue.QueueBackgroundWorkItemAsync a rendszer meghívja egy munkaelem leküldéséhez.
  • A munkaelem egy hosszú ideig futó háttérfeladatot szimulál:
    • A rendszer három 5 másodperces késést hajt végre Delay.
    • A try-catch feladat megszakítása OperationCanceledException esetén az utasítás csapódik le.
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);
        }
    }
}

Cserélje le a meglévő Program tartalmat a következő C#-kódra:

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

A szolgáltatások regisztrálva vannak (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal. MonitorLoopa Program.cs felső szintű utasításában indul el:

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

A szolgáltatások regisztrálásával kapcsolatos további információkért lásd : Függőséginjektálás a .NET-ben.

A szolgáltatás működésének ellenőrzése

Ha az alkalmazást a Visual Studióból szeretné futtatni, válassza az F5 lehetőséget, vagy válassza a Hibakeresés>indítása hibakeresés menüt. Ha a .NET CLI-t használja, futtassa a dotnet run parancsot a munkakönyvtárból:

dotnet run

A .NET CLI futtatási parancsával kapcsolatos további információkért lásd : dotnet run.

Amikor a rendszer kéri, legalább egyszer adja meg az w (vagy W) értéket egy emulált munkaelem várólistára helyezéséhez, ahogyan az a példakimenetben látható:

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.

Ha az alkalmazást a Visual Studióban futtatja, válassza a Hibakeresés>leállítása... lehetőséget. Másik lehetőségként válassza a Ctrl C billentyűkombinációt + a konzolablakban a jel törléséhez.

Kapcsolódó információk