Bagikan melalui


Membuat Layanan Antrean

Layanan antrean adalah contoh yang bagus dari layanan yang berjalan lama, di mana item kerja dapat diantrekan dan dikerjakan secara berurutan karena item kerja sebelumnya selesai. Mengandalkan templat Layanan Pekerja, Anda membangun fungsionalitas baru di atas BackgroundService.

Dalam tutorial ini, Anda akan mempelajari cara:

  • Membuat layanan antrean.
  • Mendelegasikan pekerjaan ke antrean tugas.
  • Daftarkan pendengar kunci konsol dari IHostApplicationLifetime peristiwa.

Tip

Semua kode sumber contoh "Pekerja di .NET" tersedia di Browser Sampel untuk diunduh. Untuk informasi selengkapnya, lihat Menelusuri sampel kode: Pekerja di .NET.

Prasyarat

Membuat proyek baru

Untuk membuat proyek Layanan Pekerja baru dengan Visual Studio, Anda akan memilih File>Proyek Baru>....Dari dialog Buat proyek baru untuk "Layanan Pekerja", dan pilih templat Layanan Pekerja. Jika Anda lebih suka menggunakan .NET CLI, buka terminal favorit Anda di direktori kerja. Jalankan dotnet new perintah , dan ganti dengan nama proyek yang <Project.Name> Anda inginkan.

dotnet new worker --name <Project.Name>

Untuk informasi selengkapnya tentang perintah proyek layanan pekerja baru .NET CLI, lihat dotnet pekerja baru.

Tip

Jika Anda menggunakan Visual Studio Code, Anda dapat menjalankan perintah .NET CLI dari terminal terintegrasi. Untuk informasi selengkapnya, lihat Visual Studio Code: Terminal Terintegrasi.

Membuat layanan antrean

Anda mungkin terbiasa dengan QueueBackgroundWorkItem(Func<CancellationToken,Task>) fungsionalitas dari System.Web.Hosting namespace layanan.

Tip

Fungsionalitas System.Web namespace layanan sengaja tidak di-port ke .NET, dan tetap eksklusif untuk .NET Framework. Untuk informasi selengkapnya, lihat Mulai menggunakan ASP.NET inkremental untuk ASP.NET migrasi Core.

Di .NET, untuk memodelkan layanan yang terinspirasi oleh QueueBackgroundWorkItem fungsionalitas, mulailah dengan menambahkan IBackgroundTaskQueue antarmuka ke proyek:

namespace App.QueueService;

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

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

Ada dua metode, satu yang mengekspos fungsionalitas antrean, dan satu lagi yang menghapus antrean item kerja yang sebelumnya diantrekan. Item kerja adalah Func<CancellationToken, ValueTask>. Selanjutnya, tambahkan implementasi default ke proyek.

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

Implementasi sebelumnya bergantung pada Channel<T> sebagai antrean. disebut BoundedChannelOptions(Int32) dengan kapasitas eksplisit. Kapasitas harus ditetapkan berdasarkan beban aplikasi yang diharapkan dan jumlah utas bersamaan yang mengakses antrean. BoundedChannelFullMode.Wait menyebabkan panggilan ke untuk ChannelWriter<T>.WriteAsync mengembalikan tugas, yang hanya selesai ketika ruang tersedia. Yang menyebabkan backpressure, jika terlalu banyak penerbit/panggilan mulai terakumulasi.

Menulis ulang kelas Pekerja

Dalam contoh berikut QueueHostedService :

  • Metode ProcessTaskQueueAsync mengembalikan Task dalam ExecuteAsync.
  • Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di ProcessTaskQueueAsync.
  • Item kerja ditunggu sebelum layanan berhenti di StopAsync.

Ganti kelas yang ada Worker dengan kode C# berikut, dan ganti nama file menjadi 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 Layanan menangani tugas antrean untuk layanan yang dihosting setiap kali w kunci dipilih pada perangkat input:

  • IBackgroundTaskQueue disuntikkan ke MonitorLoop dalam layanan.
  • IBackgroundTaskQueue.QueueBackgroundWorkItemAsync dipanggil untuk mengantrekan item kerja.
  • Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
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);
        }
    }
}

Ganti konten yang ada Program dengan kode C# berikut:

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

Layanan terdaftar di (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi. MonitorLoop dimulai dalam pernyataan tingkat atas Program.cs :

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

Untuk informasi selengkapnya tentang mendaftarkan layanan, lihat Injeksi dependensi di .NET.

Memverifikasi fungsionalitas layanan

Untuk menjalankan aplikasi dari Visual Studio, pilih F5 atau pilih opsi menu Debug>Mulai Debugging . Jika Anda menggunakan .NET CLI, jalankan dotnet run perintah dari direktori kerja:

dotnet run

Untuk informasi selengkapnya tentang perintah jalankan .NET CLI, lihat dotnet run.

Ketika diminta masukkan w (atau W) setidaknya sekali untuk mengantrekan item kerja yang ditimulasi, seperti yang ditunjukkan dalam contoh output:

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.

Jika menjalankan aplikasi dari dalam Visual Studio, pilih Debug>Hentikan Penelusuran Kesalahan.... Atau, pilih Ctrl + C dari jendela konsol untuk memberi sinyal pembatalan.

Baca juga