.NET での汎用ホスト
この記事では、Microsoft.Extensions.Hosting NuGet パッケージで使用できる .NET 汎用ホストを構成および構築するためのさまざまなパターンについて学習します。 .NET 汎用ホストが担当するのは、アプリの起動および有効期間の管理です。 Worker サービス テンプレートを使用すると、.NET 汎用ホスト HostApplicationBuilder が作成されます。 汎用ホストは、コンソール アプリなど、他の種類の .NET アプリケーションで使用できます。
''ホスト'' とは、次のようなアプリのリソースと有効期間機能をカプセル化するオブジェクトです。
- 依存関係の挿入 (DI)
- ログの記録
- 構成
- アプリのシャットダウン
IHostedService
の実装
ホストが起動すると、サービス コンテナーのホステッド サービスのコレクションに登録されている IHostedService の各実装で IHostedService.StartAsync が呼び出されます。 Worker サービス アプリでは、BackgroundService インスタンスを含むすべての IHostedService
実装で、BackgroundService.ExecuteAsync メソッドが呼び出されます。
アプリの相互依存するすべてのリソースを 1 つのオブジェクトに含める主な理由は、アプリの起動と正常なシャットダウンの制御の有効期間の管理のためです。
ホストを設定する
ホストは通常、Program
クラス内のコードによって構成、ビルド、および実行されます。 Main
メソッド:
- CreateApplicationBuilder メソッドを呼び出して、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();
ワーカー サービスの詳細については、「.NET のワーカー サービス」を参照してください。
ホスト ビルダーの設定
CreateApplicationBuilder メソッド:
- GetCurrentDirectory() によって返されるパスにコンテンツ ルートを設定します。
- 次から ホスト構成を読み込みます。
- プレフィックス
DOTNET_
が付いた環境変数。 - コマンド ライン引数。
- プレフィックス
- 次からアプリの構成を読み込みます。
- appsettings.json。
- appsettings.{Environment}.json。
Development
環境でアプリが実行される場合に使用されるシークレット マネージャー。- 環境変数。
- コマンド ライン引数。
- 次のログ プロバイダーを追加します。
- コンソール
- デバッグ
- EventSource
- イベント ログ (Windows で実行されている場合のみ)
- 環境が
Development
である場合は、スコープの検証と依存関係の検証を有効にします。
HostApplicationBuilder.Services は Microsoft.Extensions.DependencyInjection.IServiceCollection インスタンスです。 これらのサービスは、登録されたサービスを解決するために依存関係の注入で使用される IServiceProvider を構築するために使用されます。
フレームワークが提供するサービス
IHostBuilder.Build() または HostApplicationBuilder.Build() のどちらかを呼び出すと、次のサービスが自動的に登録されます。
追加のシナリオ ベースのホスト ビルダー
Web 用にビルドする場合、または分散型アプリケーションを作成する場合は、別のホスト ビルダーを使用することが必要な場合があります。 次の追加のホスト ビルダーの一覧について考えてみましょう。
- DistributedApplicationBuilder: 分散型アプリを作成するためのビルダー。 詳細については、「.NET Aspire」をご覧ください。
- WebApplicationBuilder: Web アプリケーションとサービスのビルダー。 詳細については、ASP.NET Core に関する記事を参照してください。
- WebHostBuilder:
IWebHost
のビルダー。 詳細については、「ASP.NET Core の Web ホスト」をご覧ください。
IHostApplicationLifetime
起動後タスクとグレースフル シャットダウン タスクを処理するために IHostApplicationLifetime サービスを任意のクラスに注入します。 インターフェイス上の 3 つのプロパティは、アプリの起動およびアプリの停止のイベント ハンドラー メソッドを登録するために使用されるキャンセル トークンです。 インターフェイスには StopApplication() メソッドも含まれています。
次の例は、IHostApplicationLifetime
イベントを登録する IHostedService と IHostedLifecycleService の実装です。
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AppLifetime.Example;
public sealed class ExampleHostedService : IHostedService, IHostedLifecycleService
{
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);
}
Task IHostedLifecycleService.StartingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("1. StartingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("2. StartAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StartedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("3. StartedAsync has been called.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("4. OnStarted has been called.");
}
private void OnStopping()
{
_logger.LogInformation("5. OnStopping has been called.");
}
Task IHostedLifecycleService.StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("6. StoppingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("7. StopAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StoppedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("8. StoppedAsync has been called.");
return Task.CompletedTask;
}
private void OnStopped()
{
_logger.LogInformation("9. OnStopped has been called.");
}
}
ExampleHostedService
実装を追加するように Worker サービス テンプレートを変更することができます。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using AppLifetime.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ExampleHostedService>();
using IHost host = builder.Build();
await host.RunAsync();
アプリケーションにより、次のサンプル出力が書き込まれます。
// Sample output:
// info: AppLifetime.Example.ExampleHostedService[0]
// 1.StartingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 2.StartAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 3.StartedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 4.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\net8.0
// info: AppLifetime.Example.ExampleHostedService[0]
// 5.OnStopping has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application is shutting down...
// info: AppLifetime.Example.ExampleHostedService[0]
// 6.StoppingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 7.StopAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 8.StoppedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 9.OnStopped has been called.
出力には、さまざまなライフサイクル イベントすべての順序が表示されます。
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
たとえば、Ctrl+C キーを使ってアプリケーションを停止すると、次のイベントが発生します。
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
IHostLifetime
IHostLifetime 実装では、ホストを開始および停止するタイミングが制御されます。 登録されている最後の実装が使用されます。 Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
は、既定の IHostLifetime
実装です。 シャットダウンの有効期間のしくみの詳細については、「ホストのシャットダウン」を参照してください。
IHostLifetime
インターフェイスは IHostLifetime.WaitForStartAsync メソッドを公開しています。これは IHost.StartAsync
の開始時に呼び出され、完了するまで待機してから続行されます。 これを使って、外部イベントによって通知されるまで開始を遅らせることができます。
さらに、IHostLifetime
インターフェイスは IHostLifetime.StopAsync メソッドを公開しています。これは、ホストが停止し、シャットダウンするタイミングであることを示すために、IHost.StopAsync
から呼び出されます。
IHostEnvironment
次の設定に関する情報を取得するため、クラスに IHostEnvironment サービスを注入します。
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
さらに、IHostEnvironment
サービスでは、次の拡張メソッドを使用して環境を評価する機能が公開されます。
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
ホストの構成
ホスト構成は、IHostEnvironment 実装のプロパティを構成するために使用されます。
ホストの構成は HostApplicationBuilderSettings.Configuration プロパティで使用でき、環境の実装は IHostApplicationBuilder.Environment プロパティで使用できます。 ホストを構成するには、Configuration
プロパティにアクセスし、使用可能な拡張メソッドのいずれかを呼び出します。
ホストの構成を追加するため、次の例を考えてみましょう。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
HostApplicationBuilderSettings settings = new()
{
Args = args,
Configuration = new ConfigurationManager(),
ContentRootPath = Directory.GetCurrentDirectory(),
};
settings.Configuration.AddJsonFile("hostsettings.json", optional: true);
settings.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
settings.Configuration.AddCommandLine(args);
HostApplicationBuilder builder = Host.CreateApplicationBuilder(settings);
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
上記のコードでは次の操作が行われます。
- GetCurrentDirectory() によって返されるパスにコンテンツ ルートを設定します。
- 次から ホスト構成を読み込みます。
- hostsettings.json.
- プレフィックス
PREFIX_
が付いた環境変数。 - コマンド ライン引数。
アプリの構成
アプリの構成は、IHostApplicationBuilder 上で ConfigureAppConfiguration を呼び出すことで作成されます。 public IHostApplicationBuilder.Configuration プロパティを使うと、コンシューマーは使用できる拡張メソッドを使って既存の構成を読み取ったり、既存の構成を変更したりできます。
詳細については、「.NET での構成」を参照してください。
ホストのシャットダウン
ホステッド プロセスを停止する方法はいくつかあります。 最も一般的には、ホステッド プロセスは次の方法で停止することができます。
- 他のユーザーが Run や HostingAbstractionsHostExtensions.WaitForShutdown を呼び出しておらず、
Main
が完了し、アプリが正常に終了した場合。 - アプリがクラッシュした場合。
- SIGKILL (または CTRL+Z) を使用して、アプリが強制的にシャットダウンされる場合。
ホスティング コードは、これらのシナリオの処理に関知しません。 プロセスの所有者は、他のアプリケーションと同じようにそれらを処理する必要があります。 ホステッド サービス プロセスを停止するための他のいくつかの方法を次に示します。
ConsoleLifetime
が使用されている場合は (UseConsoleLifetime)、これは次のシグナルをリッスンし、ホストの正常な停止を試みます。- アプリによって 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
を発生させる唯一の方法ではなかったため、このアプローチには他にも問題がありました。 SIGTERM は、アプリ コードが Environment.Exit
を呼び出すときにも発生します。 Environment.Exit
は、Microsoft.Extensions.Hosting
アプリ モデル内のプロセスをシャットダウンする適切な方法ではありません。 ProcessExit
イベントを発生させてから、プロセスを終了します。 Main
メソッドの末尾は実行されません。 バックグラウンドおよびフォアグラウンド スレッドは終了し、finally
ブロックは実行 "されません"。
ホストがシャットダウンするのを待機している間、ConsoleLifetime
によって ProcessExit
がブロックされるため、この動作により、Environment.Exit
からのデッドロックが発生し、またブロックされて、ProcessExit
の呼び出しを待機することになります。 さらに、SIGTERM 処理でプロセスの正常なシャットダウンを試みていたため、ConsoleLifetime
で ExitCode が 0
に設定され、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
メソッドはすぐに return をせず、アプリは Run
または WaitForShutdown
が return するまで実行中のままです。
シグナルがプロセスに送信された場合、次のシーケンスが開始されます。
- 制御は
ConsoleLifetime
からApplicationLifetime
に流れ、ApplicationStopping
イベントを発生させます。 このようにして、WaitForShutdownAsync
に対してMain
実行コードのブロックを解除するよう合図します。 一方、POSIX シグナル ハンドラーはCancel = true
で return します。これは POSIX シグナルが処理されたためです。 Main
実行コードではもう一度実行を開始し、ホストにStopAsync()
を指示し、その後、ホストされているサービスをすべて停止し、その他の停止イベントを発生させます。- 最後に、
WaitForShutdown
が終了し、何らかのアプリケーション クリーンアップ コードの実行が可能になり、Main
メソッドは正常に終了できます。
Web サーバー シナリオでのホストのシャットダウン
Kestrel においては、HTTP/1.1 プロトコルと HTTP/2 プロトコルの両方に関して、およびトラフィックをスムーズにドレインするためのロード バランサーを備えたさまざまな環境でそれを構成する方法に関して、正常なシャットダウンが機能する一般的なシナリオが他にもたくさんあります。 Web サーバーの構成はこの記事の範囲外ですが、ASP.NET Core Kestrel Web サーバーのオプションの構成に関するドキュメントで詳細を確認できます。
ホストは、シャットダウン信号 (たとえば、CTL+C や StopAsync
など) を受信すると、ApplicationStopping の信号を発することでアプリケーションに通知します。 正常に終了する必要がある実行時間の長い操作がある場合は、このイベントをサブスクライブする必要があります。
次に、ホストは構成が可能なシャットダウン タイムアウト (既定では 30 秒) で IServer.StopAsync を呼び出します。 Kestrel (および Http.Sys) は、ポート バインドを閉じ、新しい接続の受け入れを停止します。 また、現在の接続に対して新しい要求の処理を停止するように指示します。 HTTP/2 および HTTP/3 の場合、暫定 GOAWAY
メッセージがクライアントに送信されます。 HTTP/1.1 の場合、要求は順番に処理されるため、接続ループを停止します。 IIS の動作は異なり、503 状態コードで新しい要求を拒否します。
アクティブな要求はシャットダウン タイムアウトが完了するまで制御を保持します。 これらがすべてタイムアウト前に完了した場合、サーバーはすぐに制御をホストに返します。 タイムアウトの時間になると、保留中の接続と要求が強制的に中止され、ログとクライアントへのエラーの原因となる可能性があります。
Load Balancer に関する考慮事項
ロード バランサーを使用するときにクライアントを新しい宛先にスムーズに移行させるには、次の手順に従います。
- 新しいインスタンスを起動し、それに対してトラフィックの分散を開始します (スケーリング目的で複数のインスタンスを既に持っている場合があります)。
- ロード バランサー構成の中の古いインスタンスが新しいトラフィックの受信を停止するように、これを無効化または削除します。
- 古いインスタンスにシャットダウンを行うよう信号を送ります。
- それがドレインまたはタイムアウトするのを待ちます。
関連項目
- .NET での依存関係の挿入
- .NET でのログの記録
- .NET での構成
- .NET の Worker サービス
- ASP.NET Core の Web ホスト
- ASP.NET Core Kestrel Web サーバー構成
- ジェネリック ホストのバグは、github.com/dotnet/runtime/ リポジトリに作成する必要があります。
.NET