.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.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
ホストの構成
ホスト構成は、IHostEnvironment 実装のプロパティを構成するために使用されます。
ホスト構成は、ConfigureAppConfiguration メソッド内の HostBuilderContext.Configuration で使用できます。 メソッドを ConfigureAppConfiguration
呼び出すと、 HostBuilderContext
と IConfigurationBuilder
が に configureDelegate
渡されます。 configureDelegate
は Action<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 での構成」を参照してください。
ホストのシャットダウン
ホストされたプロセスを停止するには、いくつかの方法があります。 最も一般的に、ホストされているプロセスは次の方法で停止できます。
- 他のユーザーが 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 メソッドの の後HostingAbstractionsHostExtensions.RunのMain
コードを実行できます。
ただし、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
は返されません。または が返されるまでRun
WaitForShutdown
アプリは実行された状態を維持します。
シグナルがプロセスに送信された場合、次のシーケンスが開始されます。
- 制御は
ConsoleLifetime
からApplicationLifetime
に流れ、ApplicationStopping
イベントを発生させます。 このようにして、WaitForShutdownAsync
に対してMain
実行コードのブロックを解除するよう合図します。 それまでの間、POSIX シグナル ハンドラーは、POSIX シグナルが処理されたため、 でCancel = true
を返します。 Main
実行コードではもう一度実行を開始し、ホストにStopAsync()
を指示し、その後、ホストされているサービスをすべて停止し、その他の停止イベントを発生させます。- 最後に、
WaitForShutdown
が終了します。これにより、アプリケーションクリーンアップ コードを実行し、 メソッドをMain
正常に終了できます。
Web サーバー シナリオでのホストのシャットダウン
HTTP/1.1 プロトコルと HTTP/2 プロトコルの両方で Kestrel で正常なシャットダウンが機能する他のさまざまな一般的なシナリオと、トラフィックをスムーズにドレインするためにロード バランサーを使用してさまざまな環境で構成する方法があります。 Web サーバーの構成はこの記事の範囲外ですが、Kestrel Web サーバーの ASP.NET Coreのオプションの構成に関するドキュメントで詳細を確認できます。
ホストは、シャットダウン信号 ( CTL+C や StopAsync
など) を受信すると、 を通知してアプリケーションに ApplicationStopping通知します。 正常に完了する必要がある実行時間の長い操作がある場合は、このイベントをサブスクライブする必要があります。
次に、ホストは、構成できるシャットダウン タイムアウトを呼び出します IServer.StopAsync (既定の 30 秒)。 Kestrel (およびHttp.Sys) は、ポート バインドを閉じ、新しい接続の受け入れを停止します。 また、新しい要求の処理を停止するように現在の接続に指示します。 HTTP/2 および HTTP/3 の場合、暫定 GOAWAY
メッセージがクライアントに送信されます。 HTTP/1.1 の場合、要求は順番に処理されるため、接続ループを停止します。 IIS の動作は、503 状態コードで新しい要求を拒否することで異なります。
アクティブな要求には、シャットダウンタイムアウトが完了するまでが必要です。 タイムアウト前にすべてが完了した場合、サーバーは早く制御をホストに返します。 タイムアウトが切れると、保留中の接続と要求が強制的に中止され、ログとクライアントにエラーが発生する可能性があります。
Load Balancer に関する考慮事項
ロード バランサーを使用するときにクライアントを新しい宛先にスムーズに移行するには、次の手順に従います。
- 新しいインスタンスを起動し、それに対するトラフィックの分散を開始します (スケーリング目的で複数のインスタンスが既に存在する場合があります)。
- ロード バランサー構成で古いインスタンスを無効または削除して、新しいトラフィックの受信を停止します。
- 古いインスタンスにシャットダウンを通知します。
- ドレインまたはタイムアウトになるのを待ちます。
関連項目
- .NET での依存関係の挿入
- .NET でのログの記録
- .NET での構成
- .NET の Worker サービス
- ASP.NET Core の Web ホスト
- Kestrel Web サーバーの構成を ASP.NET Coreする
- ジェネリック ホストのバグは、github.com/dotnet/runtime/ リポジトリに作成する必要があります。