.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 サービス テンプレートを使用すると、汎用ホストを作成する次のコードが生成されます。

using Example.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

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

既定の 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 sealed 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;
using AppLifetime.Example;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
        services.AddHostedService<ExampleHostedService>())
    .Build();

await host.RunAsync();

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

// 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 呼び出すと、 HostBuilderContextIConfigurationBuilder が に configureDelegate渡されます。 configureDelegateAction<HostBuilderContext, IConfigurationBuilder> として定義されています。 このホスト ビルダー コンテキストには、IConfiguration のインスタンスである Configuration プロパティがあります。 これはホストから構築された構成を表します。一方、IConfigurationBuilder はアプリの構成に使用されるビルダー オブジェクトです。

ヒント

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

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

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

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

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureHostConfiguration(configHost =>
    {
        configHost.SetBasePath(Directory.GetCurrentDirectory());
        configHost.AddJsonFile("hostsettings.json", optional: true);
        configHost.AddEnvironmentVariables(prefix: "PREFIX_");
        configHost.AddCommandLine(args);
    })
    .Build();

// Application code should start here.

await host.RunAsync();

アプリの構成

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

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

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

ホストのシャットダウン

ホストされたプロセスを停止するには、いくつかの方法があります。 最も一般的に、ホストされているプロセスは次の方法で停止できます。

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

ホスティング コードは、これらのシナリオを処理する責任を負いません。 プロセスの所有者は、他のアプリと同じように処理する必要があります。 ホストされているサービス プロセスを停止するには、他にもいくつかの方法があります。

  • が使用されている場合 ConsoleLifetime (UseConsoleLifetime) は、次のシグナルをリッスンし、ホストを正常に停止しようとします。
    • 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 メソッドの の後HostingAbstractionsHostExtensions.RunMainコードを実行できます。

ただし、SIGTERM だけが ProcessExit 発生したため、このアプローチには他の問題がありました。 SIGTERM は、アプリ コードが を呼び出 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は返されません。または が返されるまでRunWaitForShutdownアプリは実行された状態を維持します。

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

ホスティング シャットダウン シーケンス図。

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

Web サーバー シナリオでのホストのシャットダウン

HTTP/1.1 プロトコルと HTTP/2 プロトコルの両方で Kestrel で正常なシャットダウンが機能する他のさまざまな一般的なシナリオと、トラフィックをスムーズにドレインするためにロード バランサーを使用してさまざまな環境で構成する方法があります。 Web サーバーの構成はこの記事の範囲外ですが、Kestrel Web サーバーの ASP.NET Coreのオプションの構成に関するドキュメントで詳細を確認できます。

ホストは、シャットダウン信号 ( CTL+CStopAsyncなど) を受信すると、 を通知してアプリケーションに ApplicationStopping通知します。 正常に完了する必要がある実行時間の長い操作がある場合は、このイベントをサブスクライブする必要があります。

次に、ホストは、構成できるシャットダウン タイムアウトを呼び出します IServer.StopAsync (既定の 30 秒)。 Kestrel (およびHttp.Sys) は、ポート バインドを閉じ、新しい接続の受け入れを停止します。 また、新しい要求の処理を停止するように現在の接続に指示します。 HTTP/2 および HTTP/3 の場合、暫定 GOAWAY メッセージがクライアントに送信されます。 HTTP/1.1 の場合、要求は順番に処理されるため、接続ループを停止します。 IIS の動作は、503 状態コードで新しい要求を拒否することで異なります。

アクティブな要求には、シャットダウンタイムアウトが完了するまでが必要です。 タイムアウト前にすべてが完了した場合、サーバーは早く制御をホストに返します。 タイムアウトが切れると、保留中の接続と要求が強制的に中止され、ログとクライアントにエラーが発生する可能性があります。

Load Balancer に関する考慮事項

ロード バランサーを使用するときにクライアントを新しい宛先にスムーズに移行するには、次の手順に従います。

  • 新しいインスタンスを起動し、それに対するトラフィックの分散を開始します (スケーリング目的で複数のインスタンスが既に存在する場合があります)。
  • ロード バランサー構成で古いインスタンスを無効または削除して、新しいトラフィックの受信を停止します。
  • 古いインスタンスにシャットダウンを通知します。
  • ドレインまたはタイムアウトになるのを待ちます。

関連項目