Bagikan melalui


Membuat Layanan Windows menggunakan BackgroundService

Pengembang .NET Framework mungkin terbiasa dengan aplikasi Windows Service. Sebelum .NET Core dan .NET 5+, pengembang yang mengandalkan .NET Framework dapat membuat Layanan Windows untuk melakukan tugas latar belakang atau menjalankan proses yang berjalan lama. Fungsionalitas ini masih tersedia dan Anda dapat membuat Layanan Pekerja yang berjalan sebagai Layanan Windows.

Dalam tutorial ini, Anda akan mempelajari cara:

  • Terbitkan aplikasi pekerja .NET sebagai file tunggal yang dapat dieksekusi.
  • Membuat Layanan Windows.
  • BackgroundService Buat aplikasi sebagai Layanan Windows.
  • Mulai dan hentikan Layanan Windows.
  • Lihat log peristiwa.
  • Hapus Layanan Windows.

Tip

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

Penting

Menginstal .NET SDK juga menginstal Microsoft.NET.Sdk.Worker templat dan 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.

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.

Install paket Nuget

Untuk menginteropsi Layanan Windows asli dari implementasi .NET IHostedService , Anda harus menginstal Microsoft.Extensions.Hosting.WindowsServices paket NuGet.

Untuk menginstal ini dari Visual Studio, gunakan dialog Kelola Paket NuGet... . Cari "Microsoft.Extensions.Hosting.WindowsServices", dan instal. Jika Anda lebih suka menggunakan .NET CLI, jalankan dotnet add package perintah :

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

Untuk informasi selengkapnya tentang perintah .NET CLI tambahkan paket, lihat paket penambahan dotnet.

Setelah berhasil menambahkan paket, file proyek Anda sekarang harus berisi referensi paket berikut:

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup>

Memperbarui file proyek

Proyek pekerja ini menggunakan jenis referensi C#yang dapat diubah ke null. Untuk mengaktifkannya untuk seluruh proyek, perbarui file proyek yang sesuai:

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

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WindowsService</RootNamespace>
  </PropertyGroup>

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

Perubahan file proyek sebelumnya menambahkan simpul <Nullable>enable<Nullable> . Untuk informasi selengkapnya, lihat Mengatur konteks nullable.

Buat Layanan

Tambahkan kelas baru ke proyek bernama JokeService.cs, dan ganti kontennya dengan kode C# berikut:

namespace App.WindowsService;

public sealed class JokeService
{
    public string GetJoke()
    {
        Joke joke = _jokes.ElementAt(
            Random.Shared.Next(_jokes.Count));

        return $"{joke.Setup}{Environment.NewLine}{joke.Punchline}";
    }

    // Programming jokes borrowed from:
    // https://github.com/eklavyadev/karljoke/blob/main/source/jokes.json
    private readonly HashSet<Joke> _jokes = new()
    {
        new Joke("What's the best thing about a Boolean?", "Even if you're wrong, you're only off by a bit."),
        new Joke("What's the object-oriented way to become wealthy?", "Inheritance"),
        new Joke("Why did the programmer quit their job?", "Because they didn't get arrays."),
        new Joke("Why do programmers always mix up Halloween and Christmas?", "Because Oct 31 == Dec 25"),
        new Joke("How many programmers does it take to change a lightbulb?", "None that's a hardware problem"),
        new Joke("If you put a million monkeys at a million keyboards, one of them will eventually write a Java program", "the rest of them will write Perl"),
        new Joke("['hip', 'hip']", "(hip hip array)"),
        new Joke("To understand what recursion is...", "You must first understand what recursion is"),
        new Joke("There are 10 types of people in this world...", "Those who understand binary and those who don't"),
        new Joke("Which song would an exception sing?", "Can't catch me - Avicii"),
        new Joke("Why do Java programmers wear glasses?", "Because they don't C#"),
        new Joke("How do you check if a webpage is HTML5?", "Try it out on Internet Explorer"),
        new Joke("A user interface is like a joke.", "If you have to explain it then it is not that good."),
        new Joke("I was gonna tell you a joke about UDP...", "...but you might not get it."),
        new Joke("The punchline often arrives before the set-up.", "Do you know the problem with UDP jokes?"),
        new Joke("Why do C# and Java developers keep breaking their keyboards?", "Because they use a strongly typed language."),
        new Joke("Knock-knock.", "A race condition. Who is there?"),
        new Joke("What's the best part about TCP jokes?", "I get to keep telling them until you get them."),
        new Joke("A programmer puts two glasses on their bedside table before going to sleep.", "A full one, in case they gets thirsty, and an empty one, in case they don’t."),
        new Joke("There are 10 kinds of people in this world.", "Those who understand binary, those who don't, and those who weren't expecting a base 3 joke."),
        new Joke("What did the router say to the doctor?", "It hurts when IP."),
        new Joke("An IPv6 packet is walking out of the house.", "He goes nowhere."),
        new Joke("3 SQL statements walk into a NoSQL bar. Soon, they walk out", "They couldn't find a table.")
    };
}

readonly record struct Joke(string Setup, string Punchline);

Kode sumber layanan lelucon sebelumnya mengekspos satu fungsionalitas, metode .GetJoke Ini adalah string metode pengembalian yang mewakili lelucon pemrograman acak. Bidang cakupan _jokes kelas digunakan untuk menyimpan daftar lelucon. Lelucon acak dipilih dari daftar dan dikembalikan.

Menulis ulang Worker kelas

Ganti yang sudah ada Worker dari templat dengan kode C# berikut, dan ganti nama file menjadi WindowsBackgroundService.cs:

namespace App.WindowsService;

public sealed class WindowsBackgroundService(
    JokeService jokeService,
    ILogger<WindowsBackgroundService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string joke = jokeService.GetJoke();
                logger.LogWarning("{Joke}", joke);

                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // When the stopping token is canceled, for example, a call made from services.msc,
            // we shouldn't exit with a non-zero exit code. In other words, this is expected...
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "{Message}", ex.Message);

            // Terminates this process and returns an exit code to the operating system.
            // This is required to avoid the 'BackgroundServiceExceptionBehavior', which
            // performs one of two scenarios:
            // 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
            // 2. When set to "StopHost": will cleanly stop the host, and log errors.
            //
            // In order for the Windows Service Management system to leverage configured
            // recovery options, we need to terminate the process with a non-zero exit code.
            Environment.Exit(1);
        }
    }
}

Dalam kode sebelumnya, JokeService disuntikkan bersama dengan ILogger. Keduanya tersedia untuk kelas sebagai private readonly bidang. Dalam metode , ExecuteAsync layanan lelucon meminta lelucon dan menulisnya ke pencatat. Dalam hal ini, pencatat diimplementasikan oleh Log Peristiwa Windows - Microsoft.Extensions.Logging.EventLog.EventLogLogger. Log ditulis ke, dan tersedia untuk dilihat di Pemantau Peristiwa.

Catatan

Secara default, tingkat keparahan Log Peristiwa adalah Warning. Ini dapat dikonfigurasi, tetapi untuk tujuan WindowsBackgroundService demonstrasi log dengan LogWarning metode ekstensi. Untuk menargetkan EventLog tingkat secara khusus, tambahkan entri di appsettings.{ Environment}.json, atau berikan EventLogSettings.Filter nilai.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    },
    "EventLog": {
      "SourceName": "The Joke Service",
      "LogName": "Application",
      "LogLevel": {
        "Microsoft": "Information",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
  }
}

Untuk informasi selengkapnya tentang mengonfigurasi tingkat log, lihat Penyedia pengelogan di .NET: Mengonfigurasi Windows EventLog.

Menulis ulang Program kelas

Ganti konten file Program.cs templat dengan kode C# berikut:

using App.WindowsService;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.EventLog;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
    options.ServiceName = ".NET Joke Service";
});

LoggerProviderOptions.RegisterProviderOptions<
    EventLogSettings, EventLogLoggerProvider>(builder.Services);

builder.Services.AddSingleton<JokeService>();
builder.Services.AddHostedService<WindowsBackgroundService>();

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

Metode AddWindowsService ekstensi mengonfigurasi aplikasi agar berfungsi sebagai Layanan Windows. Nama layanan diatur ke ".NET Joke Service". Layanan yang dihosting terdaftar untuk injeksi dependensi.

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

Menerbitkan aplikasi

Untuk membuat aplikasi .NET Worker Service sebagai Layanan Windows, disarankan agar Anda menerbitkan aplikasi sebagai file tunggal yang dapat dieksekusi. Kurang rentan terhadap kesalahan untuk memiliki executable mandiri, karena tidak ada file dependen yang terletak di sekitar sistem file. Tetapi Anda dapat memilih modalitas penerbitan yang berbeda, yang dapat diterima dengan sempurna, selama Anda membuat file *.exe yang dapat ditargetkan oleh Pengelola Kontrol Layanan Windows.

Penting

Pendekatan penerbitan alternatif adalah membangun *.dll (bukan *.exe), dan ketika Anda menginstal aplikasi yang diterbitkan menggunakan Windows Service Control Manager, Anda mendelegasikan ke .NET CLI dan meneruskan DLL. Untuk informasi selengkapnya, lihat .NET CLI: perintah dotnet.

sc.exe create ".NET Joke Service" binpath="C:\Path\To\dotnet.exe C:\Path\To\App.WindowsService.dll"
<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WindowsService</RootNamespace>
    <OutputType>exe</OutputType>
    <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

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

Baris sebelumnya yang disorot dari file proyek menentukan perilaku berikut:

  • <OutputType>exe</OutputType>: Membuat aplikasi konsol.
  • <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>: Mengaktifkan penerbitan file tunggal.
  • <RuntimeIdentifier>win-x64</RuntimeIdentifier>: Menentukan RID dari win-x64.
  • <PlatformTarget>x64</PlatformTarget>: Tentukan CPU platform target 64-bit.

Untuk menerbitkan aplikasi dari Visual Studio, Anda dapat membuat profil penerbitan yang dipertahankan. Profil penerbitan berbasis XML, dan memiliki ekstensi file .pubxml . Visual Studio menggunakan profil ini untuk menerbitkan aplikasi secara implisit, sedangkan jika Anda menggunakan .NET CLI — Anda harus secara eksplisit menentukan profil penerbitan untuk digunakan.

Klik kanan proyek di Penjelajah Solusi, dan pilih Terbitkan.... Lalu, pilih Tambahkan profil publikasi untuk membuat profil. Dari dialog Terbitkan, pilih Folder sebagai Target Anda.

The Visual Studio Publish dialog

Biarkan Lokasi default, lalu pilih Selesai. Setelah profil dibuat, pilih Tampilkan semua pengaturan, dan verifikasi pengaturan Profil Anda.

The Visual Studio Profile settings

Pastikan bahwa pengaturan berikut ditentukan:

  • Mode penyebaran: Mandiri
  • Menghasilkan file tunggal: diperiksa
  • Aktifkan kompilasi ReadyToRun: dicentang
  • Pangkas rakitan yang tidak digunakan (dalam pratinjau): tidak dicentang

Terakhir, pilih Terbitkan. Aplikasi ini dikompilasi, dan file .exe yang dihasilkan diterbitkan ke direktori output /publish .

Atau, Anda dapat menggunakan .NET CLI untuk menerbitkan aplikasi:

dotnet publish --output "C:\custom\publish\directory"

Untuk informasi selengkapnya, lihat dotnet publish .

Penting

Dengan .NET 6, jika Anda mencoba men-debug aplikasi dengan <PublishSingleFile>true</PublishSingleFile> pengaturan, Anda tidak akan dapat men-debug aplikasi. Untuk informasi selengkapnya, lihat Tidak dapat melampirkan ke CoreCLR saat men-debug aplikasi .NET 6 'PublishSingleFile'.

Membuat Layanan Windows

Jika Anda tidak terbiasa menggunakan PowerShell dan Anda lebih suka membuat alat penginstal untuk layanan Anda, lihat Membuat penginstal Layanan Windows. Jika tidak, untuk membuat Layanan Windows, gunakan perintah buat Windows Service Control Manager (sc.exe) asli. Jalankan PowerShell sebagai Administrator.

sc.exe create ".NET Joke Service" binpath="C:\Path\To\App.WindowsService.exe"

Tip

Jika Anda perlu mengubah akar konten konfigurasi host, Anda dapat meneruskannya sebagai argumen baris perintah saat menentukan binpath:

sc.exe create "Svc Name" binpath="C:\Path\To\App.exe --contentRoot C:\Other\Path"

Anda akan melihat pesan output:

[SC] CreateService SUCCESS

Untuk informasi selengkapnya, lihat sc.exe create.

Mengonfigurasi Layanan Windows

Setelah layanan dibuat, Anda dapat secara opsional mengonfigurasinya. Jika Anda baik-baik saja dengan default layanan, lewati ke bagian Memverifikasi fungsionalitas layanan.

Layanan Windows menyediakan opsi konfigurasi pemulihan. Anda dapat mengkueri konfigurasi saat ini menggunakan sc.exe qfailure "<Service Name>" perintah (di mana <Service Name> nama layanan Anda) untuk membaca nilai konfigurasi pemulihan saat ini:

sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS

SERVICE_NAME: .NET Joke Service
        RESET_PERIOD (in seconds)    : 0
        REBOOT_MESSAGE               :
        COMMAND_LINE                 :

Perintah akan menghasilkan konfigurasi pemulihan, yang merupakan nilai default—karena belum dikonfigurasi.

The Windows Service recovery configuration properties dialog.

Untuk mengonfigurasi pemulihan, gunakan sc.exe failure "<Service Name>" di mana <Service Name> adalah nama layanan Anda:

sc.exe failure ".NET Joke Service" reset=0 actions=restart/60000/restart/60000/run/1000
[SC] ChangeServiceConfig2 SUCCESS

Tip

Untuk mengonfigurasi opsi pemulihan, sesi terminal Anda perlu dijalankan sebagai Administrator.

Setelah berhasil dikonfigurasi, Anda dapat mengkueri nilai sekali lagi menggunakan sc.exe qfailure "<Service Name>" perintah :

sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS

SERVICE_NAME: .NET Joke Service
        RESET_PERIOD (in seconds)    : 0
        REBOOT_MESSAGE               :
        COMMAND_LINE                 :
        FAILURE_ACTIONS              : RESTART -- Delay = 60000 milliseconds.
                                       RESTART -- Delay = 60000 milliseconds.
                                       RUN PROCESS -- Delay = 1000 milliseconds.

Anda akan melihat nilai hidupkan ulang yang dikonfigurasi.

The Windows Service recovery configuration properties dialog with restart enabled.

Opsi pemulihan layanan dan instans .NET BackgroundService

Dengan .NET 6, perilaku penanganan pengecualian hosting baru telah ditambahkan ke .NET. BackgroundServiceExceptionBehavior Enum ditambahkan ke Microsoft.Extensions.Hosting namespace layanan, dan digunakan untuk menentukan perilaku layanan saat pengecualian dilemparkan. Tabel berikut ini mencantumkan opsi yang tersedia:

Opsi Deskripsi
Ignore Abaikan pengecualian yang dilemparkan dalam BackgroundService.
StopHost IHost akan dihentikan ketika pengecualian yang tidak tertangani dilemparkan.

Perilaku default sebelum .NET 6 adalah Ignore, yang menghasilkan proses zombie (proses berjalan yang tidak melakukan apa pun). Dengan .NET 6, perilaku defaultnya adalah StopHost, yang menyebabkan host dihentikan ketika pengecualian dilemparkan. Tetapi berhenti dengan bersih, yang berarti bahwa sistem manajemen Layanan Windows tidak akan memulai ulang layanan. Untuk mengizinkan layanan dimulai ulang dengan benar, Anda dapat memanggil Environment.Exit dengan kode keluar bukan nol. Pertimbangkan blok yang disorot catch berikut:

namespace App.WindowsService;

public sealed class WindowsBackgroundService(
    JokeService jokeService,
    ILogger<WindowsBackgroundService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string joke = jokeService.GetJoke();
                logger.LogWarning("{Joke}", joke);

                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // When the stopping token is canceled, for example, a call made from services.msc,
            // we shouldn't exit with a non-zero exit code. In other words, this is expected...
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "{Message}", ex.Message);

            // Terminates this process and returns an exit code to the operating system.
            // This is required to avoid the 'BackgroundServiceExceptionBehavior', which
            // performs one of two scenarios:
            // 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
            // 2. When set to "StopHost": will cleanly stop the host, and log errors.
            //
            // In order for the Windows Service Management system to leverage configured
            // recovery options, we need to terminate the process with a non-zero exit code.
            Environment.Exit(1);
        }
    }
}

Memverifikasi fungsionalitas layanan

Untuk melihat aplikasi yang dibuat sebagai Layanan Windows, buka Layanan. Pilih tombol Windows (atau Ctrl + Esc), dan cari dari "Layanan". Dari aplikasi Layanan , Anda harus dapat menemukan layanan Anda dengan namanya.

Penting

Secara default, pengguna reguler (non-admin) tidak dapat mengelola layanan Windows. Untuk memverifikasi bahwa aplikasi ini berfungsi seperti yang diharapkan, Anda harus menggunakan akun Admin.

The Services user interface.

Untuk memverifikasi bahwa layanan berfungsi seperti yang diharapkan, Anda perlu:

  • Memulai layanan
  • Menampilkan log
  • Hentikan layanannya

Penting

Untuk men-debug aplikasi, pastikan Anda tidak mencoba men-debug executable yang berjalan secara aktif dalam proses Windows Services.

Unable to start program.

Memulai Layanan Windows

Untuk memulai Layanan Windows, gunakan sc.exe start perintah :

sc.exe start ".NET Joke Service"

Anda akan melihat output yang mirip dengan berikut ini:

SERVICE_NAME: .NET Joke Service
    TYPE               : 10  WIN32_OWN_PROCESS
    STATE              : 2  START_PENDING
                            (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
    WIN32_EXIT_CODE    : 0  (0x0)
    SERVICE_EXIT_CODE  : 0  (0x0)
    CHECKPOINT         : 0x0
    WAIT_HINT          : 0x7d0
    PID                : 37636
    FLAGS

Status layanan akan beralih dari START_PENDING ke Berjalan.

Menampilkan log

Untuk melihat log, buka Pemantau Peristiwa. Pilih tombol Windows (atau Ctrl + Esc), dan cari ."Event Viewer" Pilih simpul Aplikasi Log>Pemantau Peristiwa (Lokal)>Windows. Anda akan melihat entri tingkat Peringatan dengan Sumber yang cocok dengan namespace aplikasi. Klik dua kali entri, atau klik kanan dan pilih Properti Peristiwa untuk melihat detailnya.

The Event Properties dialog, with details logged from the service

Setelah melihat log di Log Peristiwa, Anda harus menghentikan layanan. Ini dirancang untuk mencatat lelucon acak sekali per menit. Ini adalah perilaku yang disengaja tetapi tidak praktis untuk layanan produksi.

Hentikan Layanan Windows

Untuk menghentikan Layanan Windows, gunakan sc.exe stop perintah :

sc.exe stop ".NET Joke Service"

Anda akan melihat output yang mirip dengan berikut ini:

SERVICE_NAME: .NET Joke Service
    TYPE               : 10  WIN32_OWN_PROCESS
    STATE              : 3  STOP_PENDING
                            (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
    WIN32_EXIT_CODE    : 0  (0x0)
    SERVICE_EXIT_CODE  : 0  (0x0)
    CHECKPOINT         : 0x0
    WAIT_HINT          : 0x0

Status layanan akan beralih dari STOP_PENDING ke Dihentikan.

Menghapus Layanan Windows

Untuk menghapus Layanan Windows, gunakan perintah penghapusan Windows Service Control Manager (sc.exe) asli. Jalankan PowerShell sebagai Administrator.

Penting

Jika layanan tidak dalam status Dihentikan , layanan tidak akan segera dihapus. Pastikan bahwa layanan dihentikan sebelum mengeluarkan perintah hapus.

sc.exe delete ".NET Joke Service"

Anda akan melihat pesan output:

[SC] DeleteService SUCCESS

Untuk informasi selengkapnya, lihat sc.exe delete.

Baca juga

Berikutnya