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
- .NET 8.0 SDK atau yang lebih baru
- A Windows OS
- Lingkungan pengembangan terintegrasi .NET (IDE)
- Jangan ragu untuk menggunakan Visual Studio
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 dariwin-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.
Biarkan Lokasi default, lalu pilih Selesai. Setelah profil dibuat, pilih Tampilkan semua pengaturan, dan verifikasi pengaturan Profil Anda.
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.
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.
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.
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.
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.
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
- Membuat alat penginstal Layanan Windows
- Layanan Pekerja .NET
- Membuat Layanan Antrean
- Menggunakan layanan terlingkup dalam
BackgroundService
- Mengimplementasikan
IHostedService
antarmuka