.NET での汎用ホスト

Worker サービス テンプレートを使用すると、.NET 汎用ホスト HostBuilder が作成されます。 汎用ホストは、コンソール アプリなど、他の種類の .NET アプリケーションで使用できます。

''ホスト'' とは、次のようなアプリのリソースと有効期間機能をカプセル化するオブジェクトです。

  • 依存関係の挿入 (DI)
  • ログの記録
  • 構成
  • アプリのシャットダウン
  • IHostedService の実装

ホストが起動すると、サービス コンテナーのホステッド サービスのコレクションに登録されている IHostedService の各実装で IHostedService.StartAsync が呼び出されます。 Worker サービス アプリでは、BackgroundService インスタンスを含むすべての IHostedService 実装で、BackgroundService.ExecuteAsync メソッドが呼び出されます。

アプリの相互依存するすべてのリソースを 1 つのオブジェクトに含める主な理由は、アプリの起動と正常なシャットダウンの制御の有効期間の管理のためです。 これを実行するには、Microsoft.Extensions.Hosting NuGet パッケージを使用します。

ホストを設定する

ホストは通常、Program クラス内のコードによって構成、ビルド、および実行されます。 Main メソッド:

  • CreateDefaultBuilder() メソッドを呼び出して、builder オブジェクトを作成および構成します。
  • Build() を呼び出して IHost インスタンスを作成します。
  • ホスト オブジェクトに対して Run または RunAsync メソッドを呼び出します。

.NET Worker サービス テンプレートを使用すると、汎用ホストを作成する次のコードが生成されます。

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}

既定の builder 設定

CreateDefaultBuilder メソッド:

  • GetCurrentDirectory() によって返されるパスにコンテンツ ルートを設定します。
  • 次から ホスト構成を読み込みます。
    • プレフィックス DOTNET_ が付いた環境変数。
    • コマンド ライン引数。
  • 次からアプリの構成を読み込みます。
    • appsettings.json
    • appsettings.{Environment}.json
    • Development 環境でアプリが実行される場合に使用されるシークレット マネージャー。
    • 環境変数。
    • コマンド ライン引数。
  • 次のログ プロバイダーを追加します。
    • コンソール
    • デバッグ
    • EventSource
    • イベント ログ (Windows で実行されている場合のみ)
  • 環境が Development である場合は、スコープの検証と依存関係の検証を有効にします。

ConfigureServices メソッドには、サービスを Microsoft.Extensions.DependencyInjection.IServiceCollection インスタンスに追加する機能があります。 これらのサービスは、後で、依存関係の挿入から使用できるようになります。

フレームワークが提供するサービス

以下のサービスは、自動的に登録されます。

IHostApplicationLifetime

起動後タスクとグレースフル シャットダウン タスクを処理するために IHostApplicationLifetime サービスを任意のクラスに注入します。 インターフェイス上の 3 つのプロパティは、アプリの起動およびアプリの停止のイベント ハンドラー メソッドを登録するために使用されるキャンセル トークンです。 インターフェイスには StopApplication() メソッドも含まれています。

次の例は、IHostApplicationLifetime イベントを登録する IHostedService の実装です。

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

namespace AppLifetime.Example;

public class ExampleHostedService : IHostedService
{
    private readonly ILogger _logger;

    public ExampleHostedService(
        ILogger<ExampleHostedService> logger,
        IHostApplicationLifetime appLifetime)
    {
        _logger = logger;

        appLifetime.ApplicationStarted.Register(OnStarted);
        appLifetime.ApplicationStopping.Register(OnStopping);
        appLifetime.ApplicationStopped.Register(OnStopped);
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("1. StartAsync has been called.");

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("4. StopAsync has been called.");

        return Task.CompletedTask;
    }

    private void OnStarted()
    {
        _logger.LogInformation("2. OnStarted has been called.");
    }

    private void OnStopping()
    {
        _logger.LogInformation("3. OnStopping has been called.");
    }

    private void OnStopped()
    {
        _logger.LogInformation("5. OnStopped has been called.");
    }
}

ExampleHostedService 実装を追加するように Worker サービス テンプレートを変更することができます。

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

namespace AppLifetime.Example;

class Program
{
    static Task Main(string[] args) =>
        CreateHostBuilder(args).Build().RunAsync();

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((_, services) =>
                services.AddHostedService<ExampleHostedService>());
}

アプリケーションにより、次のサンプル出力が書き込まれます。

// Sample output:
//     info: ExampleHostedService[0]
//           1. StartAsync has been called.
//     info: ExampleHostedService[0]
//           2. OnStarted has been called.
//     info: Microsoft.Hosting.Lifetime[0]
//           Application started.Press Ctrl+C to shut down.
//     info: Microsoft.Hosting.Lifetime[0]
//           Hosting environment: Production
//     info: Microsoft.Hosting.Lifetime[0]
//           Content root path: ..\app-lifetime\bin\Debug\net6.0
//     info: ExampleHostedService[0]
//           3. OnStopping has been called.
//     info: Microsoft.Hosting.Lifetime[0]
//           Application is shutting down...
//     info: ExampleHostedService[0]
//           4. StopAsync has been called.
//     info: ExampleHostedService[0]
//           5. OnStopped has been called.

IHostLifetime

IHostLifetime 実装では、ホストを開始および停止するタイミングが制御されます。 登録されている最後の実装が使用されます。 Microsoft.Extensions.Hosting.Internal.ConsoleLifetime は、既定の IHostLifetime 実装です。 シャットダウンの有効期間のしくみの詳細については、「ホストのシャットダウン」を参照してください。

IHostEnvironment

次の設定に関する情報を取得するため、クラスに IHostEnvironment サービスを注入します。

ホストの構成

ホスト構成は、IHostEnvironment 実装のプロパティを構成するために使用されます。

ホスト構成は、ConfigureAppConfiguration メソッド内の HostBuilderContext.Configuration で使用できます。 ConfigureAppConfiguration メソッドを呼び出すと、HostBuilderContextIConfigurationBuilderconfigureDelegate に渡されます。 configureDelegateAction<HostBuilderContext, IConfigurationBuilder> として定義されています。 このホスト ビルダー コンテキストには、IConfiguration のインスタンスである .Configuration プロパティがあります。 これはホストから構築された構成を表します。一方、IConfigurationBuilder はアプリの構成に使用されるビルダー オブジェクトです。

ヒント

ConfigureAppConfiguration が呼び出された後、HostBuilderContext.Configurationアプリの構成に置き換えられます。

ホストの構成を追加するには、IHostBuilder 上で ConfigureHostConfiguration を呼び出します。 ConfigureHostConfiguration を複数回呼び出して結果を追加できます。 ホストは、指定されたキーで最後に値を設定したオプションを使用します。

次の例では、ホストの構成を作成します。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

class Program
{
    static async Task Main(string[] args)
    {
        using IHost host = CreateHostBuilder(args).Build();

        // Application code should start here.

        await host.RunAsync();
    }

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureHostConfiguration(configHost =>
            {
                configHost.SetBasePath(Directory.GetCurrentDirectory());
                configHost.AddJsonFile("hostsettings.json", optional: true);
                configHost.AddEnvironmentVariables(prefix: "PREFIX_");
                configHost.AddCommandLine(args);
            });
}

アプリの構成

アプリの構成は、IHostBuilder 上で ConfigureAppConfiguration を呼び出すことで作成されます。 ConfigureAppConfiguration を複数回呼び出して結果を追加できます。 アプリは、指定されたキーで最後に値を設定したオプションを使用します。

ConfigureAppConfiguration によって作成された構成は、 HostBuilderContext.Configuration で、以降の操作のために、かつ DI からのサービスとして利用できます。 ホストの構成はアプリの構成にも追加されます。

詳細については、「.NET での構成」を参照してください。

ホストのシャットダウン

次のような場合は、ホステッド サービス プロセスを停止できます。

  • 他のユーザーが RunHostingAbstractionsHostExtensions.WaitForShutdown を呼び出しておらず、Main が完了し、アプリが正常に終了した場合。
  • アプリがクラッシュした場合。
  • SIGKILL (または CTRL+Z) を使用して、アプリが強制的にシャットダウンされる場合。

これらすべてのシナリオがホスティング コードで直接処理されるわけではありません。 プロセスの所有者は、すべてのアプリケーションと同じようにそれらを扱う必要があります。 さらに次のようないくつかの場合にも、ホステッド サービス プロセスを停止することができます。

  • ConsoleLifetime が使用されている場合は、次のシグナルをリッスンし、ホストの正常な停止を試みます。
    • SIGINT (または CTRL+C)。
    • SIGQUIT (または Windows の場合は CTRL+BREAK、Unix の場合は CTRL+\)。
    • SIGTERM (docker stop など、他のアプリによって送信されるもの)。
  • アプリによって Environment.Exit が呼び出された場合。

これらのシナリオは、組み込みのホスティング ロジック (具体的には ConsoleLifetime クラス) によって処理されます。 ConsoleLifetime では、アプリケーションを正常に終了できるように、''シャットダウン'' シグナルである SIGINT、SIGQUIT、および SIGTERM の処理を試みます。

.NET 6 より前では、.NET コードで SIGTERM を適切に処理する方法がありませんでした。 この制約を回避するために、ConsoleLifetime では System.AppDomain.ProcessExit をサブスクライブします。 ProcessExit が発生すると、ConsoleLifetime ではホストに停止するよう合図し、ProcessExit スレッドをブロックし、ホストが停止するのを待機します。 これにより、アプリケーションのクリーンアップ コードで、たとえば、IHost.StopAsync を実行できます。また、Main メソッドの HostingAbstractionsHostExtensions.Run の後にコードを書くこともできます。

これにより、他の問題が発生しました。SIGTERM は、ProcessExit が発生する唯一の方法ではなかったためです。 これは、Environment.Exit を呼び出すアプリケーションのコードによっても発生します。 Environment.Exit は、Microsoft.Extensions.Hosting アプリ モデル内のプロセスをシャットダウンする適切な方法ではありません。 ProcessExit イベントを発生させてから、プロセスを終了します。 Main メソッドの末尾は実行されません。 バックグラウンドおよびフォアグラウンド スレッドは終了し、finally ブロックは実行 ''されません''。

ConsoleLifetime では ProcessExit がブロックされ、ホストがシャットダウンするのを待機していたため、この動作により、Environment.Exit からのデッドロックが発生し、またブロックされて、ProcessExit が戻るまで待機することになります。 さらに、SIGTERM 処理でプロセスの正常なシャットダウンを試みていたため、ConsoleLifetimeExitCode0 に設定され、Environment.Exit に渡されるユーザーの終了コードが上書きされました。

.NET 6 では、POSIX シグナルがサポートされ、処理されます。 これにより、ConsoleLifetime で SIGTERM を適切に処理でき、Environment.Exit が呼び出されると関係しなくなります。

ヒント

.NET 6 以上の場合、ConsoleLifetime に、シナリオ Environment.Exit を処理するロジックがなくなりました。 Environment.Exit を呼び出し、クリーンアップ ロジックを実行する必要があるアプリでは、それら自体で ProcessExit をサブスクライブできます。 このシナリオでは、ホスティングでホストの正常な停止が試みられなくなります。

アプリケーションでホスティングが使用されており、ホストを正常に停止したい場合は、Environment.Exit ではなく IHostApplicationLifetime.StopApplication を呼び出すことができます。

ホスティング シャットダウン プロセス

次のシーケンス図は、ホスティング コードで内部的にシグナルがどのように処理されるのかを示しています。 ほとんどのユーザーがこのプロセスを理解する必要はありません。 しかし、深く理解する必要がある開発者には、これが作業を開始するのに役立つ場合があります。

ホストが開始された後、ユーザーが Run または WaitForShutdown を呼び出した場合、ハンドラーが IApplicationLifetime.ApplicationStopping に登録されます。 WaitForShutdown で実行が一時停止し、ApplicationStopping イベントが発生するのを待っています。 これは、Main メソッドがすぐに戻らない方法であり、Run または WaitForShutdown が返されるまでアプリは実行されたままです。

シグナルがプロセスに送信された場合、次のシーケンスが開始されます。

Hosting shutdown sequence diagram.

  1. 制御は ConsoleLifetime から ApplicationLifetime に流れ、ApplicationStopping イベントを発生させます。 このようにして、WaitForShutdownAsync に対して Main 実行コードのブロックを解除するよう合図します。 その間、POSIX シグナル ハンドラーから Cancel = true が返されます。これは、この POSIX シグナルが処理されたためです。
  2. Main 実行コードではもう一度実行を開始し、ホストに StopAsync() を指示し、その後、ホストされているサービスをすべて停止し、その他の停止イベントを発生させます。
  3. 最後に、WaitForShutdown が終了し、アプリケーションのクリーンアップ コードを実行できるようにし、Main メソッドを正常に終了できるようにします。

関連項目