次の方法で共有


を使用して Windows サービスを作成する BackgroundService

.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 開発ワークロードがインストールされるまで非表示になります。

前提条件

新しいプロジェクトを作成する

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);
        }
    }
}

上記のコードでは、 JokeServiceILoggerと共に挿入されます。 どちらもフィールドとしてクラスで使用できます。 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 を使用している場合は、使用する発行プロファイルを明示的に指定する必要があります。

ソリューション エクスプローラーでプロジェクトを右クリックし、[発行] を選択します。 次に、[ 発行プロファイルの追加] を選択してプロファイルを作成します。 [発行] ダイアログで、ターゲットとして [フォルダー] を選択します

Visual Studio の [発行] ダイアログ

既定の 場所のままにし、[ 完了] を選択します。 プロファイルが作成されたら、[ すべての設定を表示] を選択し、 プロファイル設定を確認します。

Visual Studio プロファイルの設定

次の設定が指定されていることを確認します。

  • デプロイ モード: 自己完結型
  • 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                 :

このコマンドは、まだ構成されていないため、既定値である回復構成を出力します。

[Windows サービス回復の構成プロパティ] ダイアログ。

復旧を構成するには、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.

構成された再起動の値が表示されます。

再起動が有効になっている Windows サービス回復構成プロパティ ダイアログ。

サービス回復オプションと .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 削除」を参照してください。

こちらもご覧ください

次へ