を使用して Windows サービスを作成する
.NET Framework 開発者は、Windows サービス アプリに精通している可能性があります。 .NET Core および .NET 5 以降の前では、.NET Framework に依存していた開発者は、バックグラウンド タスクを実行したり、実行時間の長いプロセスを実行したりする Windows サービスを作成できました。 この機能は引き続き使用でき、Windows サービスとして実行される Worker Services を作成できます。
このチュートリアルで学習する内容は次のとおりです。
- 1 つのファイルの実行可能ファイルとして .NET worker アプリを発行します。
- Windows サービスを作成します。
BackgroundService
アプリを Windows サービスとして作成します。- Windows サービスを開始および停止します。
- イベント ログを表示します。
- Windows サービスを削除します。
ヒント
すべての ".NET のワーカー" サンプル ソース コードは、 サンプル ブラウザー でダウンロードできます。 詳細については、コード サンプルの参照: .NET でのワーカーに関するページをご覧ください。
重要
.NET SDK をインストールすると、 Microsoft.NET.Sdk.Worker
と worker テンプレートもインストールされます。 つまり、.NET SDK をインストールした後、dotnet new worker コマンドを使用して 新しい worker を作成できます。 Visual Studio を使用している場合、テンプレートはオプションの ASP.NET と Web 開発ワークロードがインストールされるまで非表示になります。
前提条件
- .NET 8.0 SDK 以降
- Windows OS
- .NET 統合開発環境 (IDE)
- Visual Studio を自由に使用できます
新しいプロジェクトを作成する
Visual Studio で新しい Worker Service プロジェクトを作成するには、 ファイル>New>Project...を選択します。[ 新しいプロジェクトの作成 ] ダイアログで"Worker Service" を検索し、Worker Service テンプレートを選択します。 .NET CLI を使用する場合は、作業ディレクトリでお気に入りのターミナルを開きます。 dotnet new
コマンドを実行し、<Project.Name>
を目的のプロジェクト名に置き換えます。
dotnet new worker --name <Project.Name>
.NET CLI の新しい worker サービス プロジェクト コマンドの詳細については、「 dotnet new worker」を参照してください。
ヒント
Visual Studio Code を使用している場合は、統合ターミナルから .NET CLI コマンドを実行できます。 詳細については、「 Visual Studio Code: 統合ターミナル」を参照してください。
NuGet パッケージのインストール
.NET IHostedService 実装からネイティブ Windows サービスと相互運用するには、 Microsoft.Extensions.Hosting.WindowsServices
NuGet パッケージをインストールする必要があります。
これを Visual Studio からインストールするには、[ NuGet パッケージの管理 ] ダイアログを使用します。 "Microsoft.Extensions.Hosting.WindowsServices" を検索してインストールします。 .NET CLI を使用する場合は、次のコマンドを実行します。 (SDK バージョンの .NET 9 以前を使用している場合は、代わりに dotnet add package
フォームを使用してください)。
dotnet package add Microsoft.Extensions.Hosting.WindowsServices
詳細については、「 dotnet package add」を参照してください。
パッケージを正常に追加すると、プロジェクト ファイルに次のパッケージ参照が含まれるようになります。
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
</ItemGroup>
プロジェクト ファイルの更新
このワーカー プロジェクトでは、C# の null 許容参照型を使用します。 プロジェクト全体で有効にするには、それに応じてプロジェクト ファイルを更新します。
<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="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
</ItemGroup>
</Project>
上記のプロジェクト ファイルの変更により、 <Nullable>enable<Nullable>
ノードが追加されます。 詳細については、 null 許容コンテキストの設定を参照してください。
サービスを作成する
JokeService.csという名前のプロジェクトに新しいクラスを追加し、その内容を次の C# コードに置き換えます。
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);
上記のジョーク サービスのソース コードでは、 GetJoke
メソッドという 1 つの機能が公開されています。 これは、ランダムなプログラミングジョークを表すメソッドを返す string
です。 クラス スコープの _jokes
フィールドは、ジョークのリストを格納するために使用されます。 ランダムなジョークがリストから選択され、返されます。
Worker
クラスを書き換える
テンプレートの既存の Worker
を次の C# コードに置き換え、ファイルの名前を 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);
}
}
}
上記のコードでは、 JokeService
が ILogger
と共に挿入されます。 どちらもフィールドとしてクラスで使用できます。 ExecuteAsync
メソッドでは、ジョーク サービスはジョークを要求し、ロガーに書き込みます。 この場合、ロガーは Windows イベント ログ ( Microsoft.Extensions.Logging.EventLog.EventLogLoggerProvider) によって実装されます。 ログは書き込まれ、 イベント ビューアーで表示できます。
注
既定では、 イベント ログ の重大度は Warning。 これは構成できますが、デモンストレーション目的でWindowsBackgroundService
ログをLogWarning拡張メソッドで記録します。 EventLog
レベルを具体的にターゲットにするには、appsettings にエントリを追加します。{Environment}.jsonするか、EventLogSettings.Filter値を指定します。
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"EventLog": {
"SourceName": "The Joke Service",
"LogName": "Application",
"LogLevel": {
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
}
ログ レベルの構成の詳細については、「 .NET のログ プロバイダー: Windows EventLog の構成」を参照してください。
Program
クラスを書き換える
テンプレートProgram.csファイル の 内容を次の C# コードに置き換えます。
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();
AddWindowsService
拡張メソッドは、Windows サービスとして動作するようにアプリを構成します。 サービス名は ".NET Joke Service"
に設定されます。 ホストされるサービスは、依存関係の挿入用に登録されます。
サービスの登録の詳細については、「 .NET での依存関係の挿入」を参照してください。
アプリを公開する
.NET Worker Service アプリを Windows サービスとして作成するには、アプリを 1 つのファイル実行可能ファイルとして発行することをお勧めします。 ファイル システムの周囲に依存ファイルが存在しないため、自己完結型の実行可能ファイルを使用する場合のエラーが少なくなります。 ただし、Windows サービス コントロール マネージャーの対象となる *.exe ファイルを作成する限り、別の公開モダリティを選択することもできます。これは完全に許容されます。
重要
別の発行方法として 、*.dll ( *.exeではなく) をビルドし、Windows サービス コントロール マネージャーを使用して発行済みアプリをインストールする場合は、.NET CLI に委任し、DLL を渡します。 詳細については、「 .NET CLI: 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="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
</ItemGroup>
</Project>
プロジェクト ファイルの上記の強調表示された行は、次の動作を定義します。
<OutputType>exe</OutputType>
: コンソール アプリケーションを作成します。<PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
: 単一ファイルの発行を有効にします。<RuntimeIdentifier>win-x64</RuntimeIdentifier>
: のwin-x64
指定します。<PlatformTarget>x64</PlatformTarget>
: 64 ビットのターゲット プラットフォーム CPU を指定します。
Visual Studio からアプリを発行するには、永続化される発行プロファイルを作成します。 発行プロファイルは XML ベースであり、 .pubxml ファイル拡張子を持ちます。 Visual Studio ではこのプロファイルを使用してアプリを暗黙的に発行しますが、.NET CLI を使用している場合は、使用する発行プロファイルを明示的に指定する必要があります。
ソリューション エクスプローラーでプロジェクトを右クリックし、[発行] を選択します。 次に、[ 発行プロファイルの追加] を選択してプロファイルを作成します。 [発行] ダイアログで、ターゲットとして [フォルダー] を選択します。
既定の 場所のままにし、[ 完了] を選択します。 プロファイルが作成されたら、[ すべての設定を表示] を選択し、 プロファイル設定を確認します。
次の設定が指定されていることを確認します。
- デプロイ モード: 自己完結型
- 1 つのファイルを生成する: オン
- ReadyToRun コンパイルを有効にする: オン
- 未使用のアセンブリをトリミングする (プレビュー段階): オフ
最後に、[発行] を選択 します。 アプリがコンパイルされ、結果の .exe ファイルが /publish 出力ディレクトリに発行されます。
または、.NET CLI を使用してアプリを発行することもできます。
dotnet publish --output "C:\custom\publish\directory"
詳細については、dotnet publish
を参照してください。
重要
.NET 6 では、 <PublishSingleFile>true</PublishSingleFile>
設定でアプリをデバッグしようとすると、アプリをデバッグできなくなります。 詳細については、「 PublishSingleFile」 .NET 6 アプリをデバッグするときに CoreCLR にアタッチできないを参照してください。
Windows サービスを作成する
PowerShell の使用に慣れていない場合に、サービスのインストーラーを作成する場合は、「 Windows サービス インストーラーの作成」を参照してください。 それ以外の場合、Windows サービスを作成するには、ネイティブ Windows サービス コントロール マネージャー (sc.exe) create コマンドを使用します。 PowerShell を管理者として実行します。
sc.exe create ".NET Joke Service" binpath= "C:\Path\To\App.WindowsService.exe"
ヒント
ホスト構成のコンテンツ ルートを変更する必要がある場合は、binpath
を指定するときにコマンド ライン引数として渡すことができます。
sc.exe create "Svc Name" binpath= "C:\Path\To\App.exe --contentRoot C:\Other\Path"
出力メッセージが表示されます。
[SC] CreateService SUCCESS
詳細については、「 sc.exe 作成」を参照してください。
Windows サービスを構成する
サービスが作成されたら、必要に応じて構成できます。 サービスの既定値に問題がない場合は、「 サービス機能の確認 」セクションに進んでください。
Windows サービスには、回復構成オプションが用意されています。 sc.exe qfailure "<Service Name>"
(サービスの名前は<Service Name>
) コマンドを使用して現在の構成に対してクエリを実行し、現在の回復構成値を読み取ることができます。
sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: .NET Joke Service
RESET_PERIOD (in seconds) : 0
REBOOT_MESSAGE :
COMMAND_LINE :
このコマンドは、まだ構成されていないため、既定値である回復構成を出力します。
復旧を構成するには、sc.exe failure "<Service Name>"
を使用します。ここで、<Service Name>
はサービスの名前です。
sc.exe failure ".NET Joke Service" reset= 0 actions= restart/60000/restart/60000/run/1000
[SC] ChangeServiceConfig2 SUCCESS
ヒント
回復オプションを構成するには、ターミナル セッションを管理者として実行する必要があります。
構成が正常に完了したら、 sc.exe qfailure "<Service Name>"
コマンドを使用して値のクエリを再度実行できます。
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.
構成された再起動の値が表示されます。
サービス回復オプションと .NET BackgroundService
インスタンス
.NET 6 では、 新しいホスティング例外処理動作 が .NET に追加されました。 BackgroundServiceExceptionBehavior列挙型はMicrosoft.Extensions.Hosting
名前空間に追加され、例外がスローされたときにサービスの動作を指定するために使用されます。 次の表は、使用可能な値の一覧です。
選択肢 | 説明 |
---|---|
Ignore | BackgroundService でスローされた例外を無視します。 |
StopHost | ハンドルされない例外がスローされると、IHost は停止します。 |
.NET 6 より前の既定の動作は Ignore
。その結果、 ゾンビ プロセス (何も行わなかった実行中のプロセス) が発生しました。 .NET 6 では、既定の動作は StopHost
であり、例外がスローされたときにホストが停止します。 ただし、これは正常に停止します。つまり、Windows サービス管理システムはサービスを再起動しません。 サービスの再起動を正しく許可するには、0 以外の終了コードを使用して Environment.Exit を呼び出すことができます。 次の強調表示された catch
ブロックについて考えてみましょう。
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);
}
}
}
サービス機能を確認する
Windows サービスとして作成されたアプリを表示するには、[ サービス] を開きます。 Windows キー (または Ctrl + Esc) を選択し、[サービス] から検索します。 サービス アプリから、その名前でサービスを見つけることができます。
重要
既定では、通常の (管理者以外の) ユーザーは Windows サービスを管理できません。 このアプリが期待どおりに機能することを確認するには、管理者アカウントを使用する必要があります。
サービスが期待どおりに機能していることを確認するには、次の手順を実行する必要があります。
- サービスを開始する
- ログを表示する
- サービスを停止する
重要
アプリケーションをデバッグするには、Windows サービス プロセス内でアクティブに実行されている実行可能ファイルをデバッグしようと していないことを 確認します。
Windows サービスを開始する
Windows サービスを開始するには、 sc.exe start
コマンドを使用します。
sc.exe start ".NET Joke Service"
次のような出力が表示されます。
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
サービスの 状態 は、 START_PENDING
から 実行中に移行します。
ログの表示
ログを表示するには、 イベント ビューアーを開きます。 Windows キー (または Ctrl + Esc) を選択し、 "Event Viewer"
を検索します。 [イベント ビューアー (ローカル)]>Windows Logs>Application ノードを選択します。 アプリの名前空間に一致するソースを含む警告レベルのエントリが表示されます。 エントリをダブルクリックするか、右クリックして [ イベントのプロパティ ] を選択して詳細を表示します。
イベント ログにログが表示されたら、サービスを停止する必要があります。 1 分に 1 回ランダムなジョークを記録するように設計されています。 これは意図的な動作ですが、運用サービスには実用的 ではありません 。
Windows サービスを停止する
Windows サービスを停止するには、 sc.exe stop
コマンドを使用します。
sc.exe stop ".NET Joke Service"
次のような出力が表示されます。
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
サービスの 状態 が STOP_PENDING
から停止に移行 します。
Windows サービスを削除する
Windows サービスを削除するには、ネイティブ Windows サービス コントロール マネージャー (sc.exe) delete コマンドを使用します。 PowerShell を管理者として実行します。
重要
サービスが 停止 状態でない場合、すぐには削除されません。 delete コマンドを発行する前に、サービスが停止していることを確認します。
sc.exe delete ".NET Joke Service"
出力メッセージが表示されます。
[SC] DeleteService SUCCESS
詳細については、「 sc.exe 削除」を参照してください。
こちらもご覧ください
- Windows サービス インストーラーを作成する
- .NET の Worker サービス
- キュー サービスを作成する
- 内でスコープ付きサービスを使用する
BackgroundService
IHostedService
インターフェイスを実装する
次へ
.NET