Bagikan melalui


Layanan pekerja di .NET

Ada banyak alasan untuk membuat layanan jangka panjang seperti:

  • Memproses data yang membebani CPU.
  • Mengantrekan item kerja di latar belakang.
  • Melaksanakan operasi berbasis waktu sesuai dengan jadwal.

Pemrosesan layanan latar belakang biasanya tidak melibatkan antarmuka pengguna (UI), tetapi UI dapat dibangun di sekitarnya. Pada hari-hari awal dengan .NET Framework, pengembang Windows dapat membuat Layanan Windows untuk tujuan ini. Sekarang dengan .NET, Anda dapat menggunakan BackgroundService, yang merupakan implementasi dari IHostedService, atau menerapkan milik Anda sendiri.

Dengan .NET, Anda tidak lagi dibatasi untuk Windows. Anda dapat mengembangkan layanan latar belakang lintas platform. Layanan yang dihosting siap untuk pengelogan, konfigurasi, dan injeksi dependensi (DI). Mereka adalah bagian dari rangkaian pustaka ekstensi, yang berarti mereka mendasar untuk semua beban kerja .NET yang bekerja dengan host generik .

Penting

Menginstal .NET SDK juga menginstal Microsoft.NET.Sdk.Worker dan template pekerja. Dengan kata lain, setelah menginstal .NET SDK, Anda dapat membuat pekerja baru dengan menggunakan perintah dotnet new worker. Jika Anda menggunakan Visual Studio, templat disembunyikan hingga ASP.NET opsional dan beban kerja pengembangan web diinstal.

Terminologi

Banyak istilah yang keliru digunakan secara sinonim. Bagian ini mendefinisikan beberapa istilah ini untuk membuat niat mereka dalam artikel ini lebih jelas.

  • Layanan Latar Belakang : Jenis BackgroundService.
  • Hosted Service: Implementasi dari IHostedService atau IHostedService itu sendiri.
  • Layanan Jangka Panjang: Layanan apa pun yang berjalan terus menerus.
  • Windows Service : Infrastruktur Layanan Windows, awalnya .NET Framework-centric tetapi sekarang dapat diakses melalui .NET.
  • Layanan Pekerja: Templat Layanan Pekerja.

Templat Layanan Pekerja

Templat Layanan Pekerja tersedia di .NET CLI dan Visual Studio. Untuk informasi selengkapnya, lihat .NET CLI, dotnet new worker - templat. Templat terdiri dari kelas Program dan Worker.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

Kelas Program sebelumnya:

Pengaturan dasar templat

Templat Pekerja tidak mengaktifkan pengumpulan sampah server (GC) secara default, karena ada banyak faktor yang berperan dalam menentukan kebutuhannya. Semua skenario yang memerlukan layanan jangka panjang harus mempertimbangkan implikasi performa dari default ini. Untuk mengaktifkan GC server, tambahkan simpul ServerGarbageCollection ke file proyek:

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Pertukaran dan pertimbangan

Diaktifkan Dinonaktifkan
Manajemen memori yang efisien: Secara otomatis mengklaim kembali memori yang tidak digunakan untuk mencegah kebocoran memori dan mengoptimalkan penggunaan sumber daya. Peningkatan performa real time: Menghindari potensi jeda atau gangguan yang disebabkan oleh pengumpulan sampah dalam aplikasi yang sensitif terhadap latensi.
Stabilitas jangka panjang: Membantu mempertahankan performa yang stabil dalam layanan jangka panjang dengan mengelola memori selama periode yang lama. Efisiensi sumber daya: Dapat menghemat sumber daya CPU dan memori di lingkungan yang dibatasi sumber daya.
Mengurangi pemeliharaan: Meminimalkan kebutuhan akan manajemen memori manual, menyederhanakan pemeliharaan. Kontrol memori manual: Menyediakan kontrol terperinci atas memori untuk aplikasi khusus.
Perilaku yang dapat diprediksi: Berkontribusi pada perilaku aplikasi yang konsisten dan dapat diprediksi. Cocok untuk proses berumur pendek: Meminimalkan overhead pengumpulan sampah untuk proses berumur pendek atau sementara.

Untuk informasi selengkapnya mengenai pertimbangan performa, lihat Server GC. Untuk informasi selengkapnya tentang mengonfigurasi GC server, lihat contoh konfigurasi GC Server .

Kelas pekerja

Adapun Worker, templat menyediakan implementasi sederhana.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Kelas Worker sebelumnya adalah subkelas BackgroundService, yang mengimplementasikan IHostedService. BackgroundService adalah abstract class dan memerlukan subkelas untuk menerapkan BackgroundService.ExecuteAsync(CancellationToken). Dalam implementasi templat, ExecuteAsync loop berulang sekali per detik, mencatat tanggal dan waktu saat ini hingga proses diberi sinyal untuk dibatalkan.

File proyek

Templat Pekerja bergantung pada file proyek berikut Sdk:

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

Untuk informasi selengkapnya, lihat .NET project SDK.

Paket NuGet

Aplikasi berdasarkan templat Pekerja menggunakan SDK Microsoft.NET.Sdk.Worker dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting.

Kontainer dan adaptabilitas terhadap awan

Dengan sebagian besar beban kerja .NET modern, kontainer adalah opsi yang layak. Saat membuat layanan jangka panjang dari template Worker di Visual Studio, Anda dapat memilih untuk dukungan Docker. Melakukannya membuat Dockerfile yang menempatkan aplikasi .NET Anda dalam kontainer. Dockerfile adalah serangkaian instruksi untuk membangun image. Untuk aplikasi .NET, Dockerfile biasanya berada di akar direktori di samping file solusi.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

Langkah-langkah Dockerfile sebelumnya meliputi:

  • Mengatur gambar dasar dari mcr.microsoft.com/dotnet/runtime:8.0 sebagai alias base.
  • Mengubah direktori kerja menjadi /app.
  • Mengatur alias build dari gambar mcr.microsoft.com/dotnet/sdk:8.0.
  • Mengubah direktori kerja menjadi /src.
  • Menyalin konten dan menerbitkan aplikasi .NET:
  • Lapisan ulang gambar .NET SDK dari mcr.microsoft.com/dotnet/runtime:8.0 ( base alias).
  • Menyalin output build yang telah diterbitkan dari /publish.
  • Menentukan titik masuk, yang mendelegasikan ke dotnet App.BackgroundService.dll.

Saran

MCR di mcr.microsoft.com adalah singkatan dari "Microsoft Container Registry", dan merupakan katalog kontainer sindikasi Microsoft dari hub Docker resmi. Artikel katalog kontainer sindikat Microsoft isinya berisi detail tambahan.

Saat Anda menargetkan Docker sebagai strategi penyebaran untuk Layanan Pekerja .NET Anda, ada beberapa pertimbangan dalam file proyek:

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

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

Dalam file proyek sebelumnya, elemen <DockerDefaultTargetOS> menentukan Linux sebagai targetnya. Untuk menargetkan kontainer Windows, gunakan Windows sebagai gantinya. Paket Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet secara otomatis ditambahkan sebagai referensi paket saat dukungan Docker dipilih dari templat.

Untuk informasi selengkapnya tentang Docker dengan .NET, lihat Tutorial : Membuat kontainer aplikasi .NET. Untuk informasi selengkapnya tentang penyebaran ke Azure, lihat Tutorial : Menyebarkan Layanan Pekerja ke Azure.

Penting

Jika Anda ingin memanfaatkan Rahasia Pengguna dengan templat Pekerja, Anda harus secara eksplisit mereferensikan paket Microsoft.Extensions.Configuration.UserSecrets NuGet.

Ekstensibilitas Layanan yang Dihosting

Antarmuka IHostedService mendefinisikan dua metode:

Kedua metode ini berfungsi sebagai metode siklus hidup - mereka dipanggil selama peristiwa mulai dan berhenti host.

Nota

Saat mengambil alih metode StartAsync atau StopAsync, Anda harus memanggil dan await metode kelas base untuk memastikan layanan dimulai dan/atau dimatikan dengan benar.

Penting

Antarmuka berfungsi sebagai batasan parameter jenis generik pada metode ekstensi AddHostedService<THostedService>(IServiceCollection), yang berarti hanya implementasi yang diizinkan. Anda bebas menggunakan BackgroundService yang disediakan dengan subkelas, atau mengimplementasikan sendiri sepenuhnya.

Sinyal Selesai

Dalam skenario yang paling umum, Anda tidak perlu secara eksplisit memberi sinyal penyelesaian layanan yang dihosting. Ketika host memulai layanan, layanan tersebut dirancang untuk berjalan hingga host tersebut dihentikan. Namun, dalam beberapa skenario, Anda mungkin perlu memberi sinyal penyelesaian seluruh aplikasi host ketika layanan selesai. Untuk memberi sinyal penyelesaian, pertimbangkan kelas Worker berikut:

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

Dalam kode sebelumnya, metode BackgroundService.ExecuteAsync(CancellationToken) tidak mengulang, dan ketika selesai, ia memanggil IHostApplicationLifetime.StopApplication().

Penting

Ini akan memberi sinyal kepada host bahwa itu harus berhenti, dan tanpa panggilan ke StopApplication, host akan tetap berjalan tanpa batas waktu. Jika Anda ingin menjalankan layanan yang dihosting berumur pendek (skenario menjalankan sekali), dan Anda ingin menggunakan templat Worker, Anda harus memanggil StopApplication untuk memberi sinyal kepada host untuk berhenti.

Untuk informasi selengkapnya, lihat:

Pendekatan alternatif

Untuk aplikasi jangka pendek yang membutuhkan injeksi dependensi, pencatatan, dan konfigurasi, gunakan Host Generik .NET alih-alih template Worker. Ini memungkinkan Anda menggunakan fitur-fitur ini tanpa Worker kelas . Contoh sederhana aplikasi berumur pendek menggunakan host generik mungkin menentukan file proyek seperti berikut:

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

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ShortLived.App</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
  </ItemGroup>
</Project>

Kelas ini Program mungkin terlihat seperti berikut:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();

using var host = builder.Build();

try
{
    var runner = host.Services.GetRequiredService<JobRunner>();

    await runner.RunAsync();

    return 0; // success
}
catch (Exception ex)
{
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    
    logger.LogError(ex, "Unhandled exception occurred during job execution.");

    return 1; // failure
}

Kode sebelumnya membuat JobRunner layanan, yang merupakan kelas kustom yang berisi logika untuk pekerjaan yang akan dijalankan. Metode RunAsync ini dipanggil pada JobRunner, dan jika berhasil diselesaikan, aplikasi mengembalikan 0. Jika terjadi pengecualian yang tidak tertangani, maka akan mencatat kesalahan dan mengembalikan 1.

Dalam skenario sederhana ini, JobRunner kelas bisa terlihat seperti ini:

using Microsoft.Extensions.Logging;

internal sealed class JobRunner(ILogger<JobRunner> logger)
{
    public async Task RunAsync()
    {
        logger.LogInformation("Starting job...");

        // Simulate work
        await Task.Delay(1000);

        // Simulate failure
        // throw new InvalidOperationException("Something went wrong!");

        logger.LogInformation("Job completed successfully.");
    }
}

Anda jelas perlu menambahkan logika nyata ke metode RunAsync, tetapi contoh ini menunjukkan cara menggunakan host generik untuk aplikasi berumur pendek tanpa perlu kelas Worker, dan tanpa perlu secara eksplisit memberi sinyal penyelesaian tuan rumah.

Lihat juga