Tugas latar belakang dengan layanan yang dihosting di ASP.NET Core

Oleh Jeow Li Huan

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:

  • Tugas latar belakang yang berjalan pada timer.
  • Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
  • Tugas latar belakang antrean yang berjalan secara berurutan.

Templat Layanan Pekerja

Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:

  1. Buat proyek baru.
  2. Pilih Layanan Pekerja. Pilih Selanjutnya.
  3. Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
  4. Dalam dialog Informasi tambahan, Pilih Kerangka Kerja. Pilih Buat.

Paket

Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj).

Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.

Antarmuka IHostedService

Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:

StartAsync

StartAsync(CancellationToken) berisi logika untuk memulai tugas latar belakang. StartAsync dipanggil sebelumnya:

StartAsync harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync eksekusi hingga selesai.

StopAsync

Token pembatalan memiliki batas waktu default 30 detik untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:

  • Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
  • Metode apa pun yang dipanggil StopAsync harus segera kembali.

Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.

Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync mungkin tidak terjadi.

Untuk memperpanjang batas waktu mati 30 detik default, atur:

Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose harus dipanggil meskipun StopAsync tidak dipanggil.

Kelas dasar BackgroundService

BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.

ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync untuk selesai.

Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.

Untuk informasi selengkapnya, lihat kode sumber BackgroundService .

Tugas latar belakang berwaktu

Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork . Timer dinonaktifkan dan StopAsync dibuang ketika kontainer layanan dibuang pada Dispose:

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer = null;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Timer Tidak menunggu eksekusi DoWork sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount secara bersamaan.

Layanan ini terdaftar di IHostBuilder.ConfigureServices (Program.cs) dengan AddHostedService metode ekstensi:

services.AddHostedService<TimedHostedService>();

Menggunakan layanan tercakup dalam tugas latar belakang

Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.

Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:

  • Layanan ini asinkron. Metode mengembalikan DoWorkTask. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
  • Disuntikkan ILogger ke dalam layanan.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;

            _logger.LogInformation(
                "Scoped Processing Service is working. Count: {Count}", executionCount);

            await Task.Delay(10000, stoppingToken);
        }
    }
}

Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork . DoWorkTaskmengembalikan , yang ditunggu dalam ExecuteAsync:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Tugas latar belakang yang diantrekan

Antrean tugas latar belakang didasarkan pada .NET 4.x QueueBackgroundWorkItem:

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

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Dalam contoh berikut QueueHostedService :

  • Metode mengembalikan BackgroundProcessingTask, yang ditunggu dalam ExecuteAsync.
  • Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di BackgroundProcessing.
  • Item kerja ditunggu sebelum layanan berhenti di StopAsync.
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service 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.QueueBackgroundWorkItem dipanggil untuk mengantrekan item kerja.
  • Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue,
        ILogger<MonitorLoop> logger,
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("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(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

        _logger.LogInformation("Queued Background Task {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 Background Task {Guid} is running. " 
                                   + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop dimulai di Program.cs:

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

Tugas latar belakang berwaktu asinkron

Kode berikut membuat tugas latar belakang berwaktu asinkron:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        // When the timer should have no due-time, then do the work once now.
        DoWork();

        using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));

        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                DoWork();
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Timed Hosted Service is stopping.");
        }
    }

    // Could also be a async method, that can be awaited in ExecuteAsync above
    private void DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

        _logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
    }
}

AOT Asli

Templat Layanan Pekerja mendukung .NET native ahead-of-time (AOT) dengan --aot bendera:

  1. Buat proyek baru.
  2. Pilih Layanan Pekerja. Pilih Selanjutnya.
  3. Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
  4. Dalam dialog Informasi tambahan:
  5. Pilih Kerangka Kerja.
  6. Centang kotak centang Aktifkan penerbitan AOT Asli.
  7. Pilih Buat.

Opsi AOT menambahkan <PublishAot>true</PublishAot> ke file proyek:


<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
+   <PublishAot>true</PublishAot>
    <UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
  </ItemGroup>
</Project>

Sumber Daya Tambahan:

Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:

  • Tugas latar belakang yang berjalan pada timer.
  • Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
  • Tugas latar belakang antrean yang berjalan secara berurutan.

Templat Layanan Pekerja

Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:

  1. Buat proyek baru.
  2. Pilih Layanan Pekerja. Pilih Selanjutnya.
  3. Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
  4. Dalam dialog Informasi tambahan, Pilih Kerangka Kerja. Pilih Buat.

Paket

Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj).

Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.

Antarmuka IHostedService

Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:

StartAsync

StartAsync(CancellationToken) berisi logika untuk memulai tugas latar belakang. StartAsync dipanggil sebelumnya:

StartAsync harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync eksekusi hingga selesai.

StopAsync

Token pembatalan memiliki batas waktu default 30 detik untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:

  • Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
  • Metode apa pun yang dipanggil StopAsync harus segera kembali.

Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.

Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync mungkin tidak terjadi.

Untuk memperpanjang batas waktu mati 30 detik default, atur:

Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose harus dipanggil meskipun StopAsync tidak dipanggil.

Kelas dasar BackgroundService

BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.

ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync untuk selesai.

Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.

Untuk informasi selengkapnya, lihat kode sumber BackgroundService .

Tugas latar belakang berwaktu

Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork . Timer dinonaktifkan dan StopAsync dibuang ketika kontainer layanan dibuang pada Dispose:

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer = null;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Timer Tidak menunggu eksekusi DoWork sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount secara bersamaan.

Layanan ini terdaftar di IHostBuilder.ConfigureServices (Program.cs) dengan AddHostedService metode ekstensi:

services.AddHostedService<TimedHostedService>();

Menggunakan layanan tercakup dalam tugas latar belakang

Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.

Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:

  • Layanan ini asinkron. Metode mengembalikan DoWorkTask. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
  • Disuntikkan ILogger ke dalam layanan.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;

            _logger.LogInformation(
                "Scoped Processing Service is working. Count: {Count}", executionCount);

            await Task.Delay(10000, stoppingToken);
        }
    }
}

Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork . DoWorkTaskmengembalikan , yang ditunggu dalam ExecuteAsync:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Tugas latar belakang yang diantrekan

Antrean tugas latar belakang didasarkan pada .NET 4.x QueueBackgroundWorkItem:

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

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Dalam contoh berikut QueueHostedService :

  • Metode mengembalikan BackgroundProcessingTask, yang ditunggu dalam ExecuteAsync.
  • Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di BackgroundProcessing.
  • Item kerja ditunggu sebelum layanan berhenti di StopAsync.
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service 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.QueueBackgroundWorkItem dipanggil untuk mengantrekan item kerja.
  • Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue,
        ILogger<MonitorLoop> logger,
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("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(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

        _logger.LogInformation("Queued Background Task {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 Background Task {Guid} is running. " 
                                   + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop dimulai di Program.cs:

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

Tugas latar belakang berwaktu asinkron

Kode berikut membuat tugas latar belakang berwaktu asinkron:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        // When the timer should have no due-time, then do the work once now.
        DoWork();

        using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));

        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                DoWork();
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Timed Hosted Service is stopping.");
        }
    }

    // Could also be a async method, that can be awaited in ExecuteAsync above
    private void DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

        _logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
    }
}

Sumber Daya Tambahan:

Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:

  • Tugas latar belakang yang berjalan pada timer.
  • Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
  • Tugas latar belakang antrean yang berjalan secara berurutan.

Melihat atau mengunduh kode sampel (cara mengunduh)

Templat Layanan Pekerja

Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:

  1. Buat proyek baru.
  2. Pilih Layanan Pekerja. Pilih Selanjutnya.
  3. Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Buat.
  4. Dalam dialog Buat layanan Pekerja baru, pilih Buat.

Paket

Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj).

Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.

Antarmuka IHostedService

Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:

StartAsync

StartAsync berisi logika untuk memulai tugas latar belakang. StartAsync dipanggil sebelumnya:

Perilaku default dapat diubah sehingga layanan StartAsync yang dihosting berjalan setelah alur aplikasi dikonfigurasi dan ApplicationStarted dipanggil. Untuk mengubah perilaku default, tambahkan layanan yang dihosting (VideosWatcher dalam contoh berikut) setelah memanggil ConfigureWebHostDefaults:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .ConfigureServices(services =>
            {
                services.AddHostedService<VideosWatcher>();
            });
}

StopAsync

Token pembatalan memiliki batas waktu lima detik default untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:

  • Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
  • Metode apa pun yang dipanggil StopAsync harus segera kembali.

Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.

Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync mungkin tidak terjadi.

Untuk memperpanjang batas waktu mati lima detik default, atur:

Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose harus dipanggil meskipun StopAsync tidak dipanggil.

Kelas dasar BackgroundService

BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.

ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync untuk selesai.

Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.

StartAsync harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync eksekusi hingga selesai. Tugas yang berjalan lama harus ditempatkan di ExecuteAsync. Untuk informasi selengkapnya, lihat sumber ke BackgroundService.

Tugas latar belakang berwaktu

Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork . Timer dinonaktifkan dan StopAsync dibuang ketika kontainer layanan dibuang pada Dispose:

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Timer Tidak menunggu eksekusi DoWork sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount secara bersamaan.

Layanan ini terdaftar di IHostBuilder.ConfigureServices (Program.cs) dengan AddHostedService metode ekstensi:

services.AddHostedService<TimedHostedService>();

Menggunakan layanan tercakup dalam tugas latar belakang

Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.

Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:

  • Layanan ini asinkron. Metode mengembalikan DoWorkTask. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
  • Disuntikkan ILogger ke dalam layanan.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;

            _logger.LogInformation(
                "Scoped Processing Service is working. Count: {Count}", executionCount);

            await Task.Delay(10000, stoppingToken);
        }
    }
}

Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork . DoWorkTaskmengembalikan , yang ditunggu dalam ExecuteAsync:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Tugas latar belakang yang diantrekan

Antrean tugas latar belakang didasarkan pada .NET 4.x QueueBackgroundWorkItem:

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

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Dalam contoh berikut QueueHostedService :

  • Metode mengembalikan BackgroundProcessingTask, yang ditunggu dalam ExecuteAsync.
  • Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di BackgroundProcessing.
  • Item kerja ditunggu sebelum layanan berhenti di StopAsync.
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service 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.QueueBackgroundWorkItem dipanggil untuk mengantrekan item kerja.
  • Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("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(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

        _logger.LogInformation("Queued Background Task {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 Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

Layanan terdaftar di IHostBuilder.ConfigureServices (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService metode ekstensi:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop dimulai di Program.Main:

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

Sumber Daya Tambahan: