.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.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;
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 での構成」を参照してください。
ホストのシャットダウン
次のような場合は、ホステッド サービス プロセスを停止できます。
- 他のユーザーが Run や HostingAbstractionsHostExtensions.WaitForShutdown を呼び出しておらず、
Main
が完了し、アプリが正常に終了した場合。 - アプリがクラッシュした場合。
- SIGKILL (または CTRL+Z) を使用して、アプリが強制的にシャットダウンされる場合。
これらすべてのシナリオがホスティング コードで直接処理されるわけではありません。 プロセスの所有者は、すべてのアプリケーションと同じようにそれらを扱う必要があります。 さらに次のようないくつかの場合にも、ホステッド サービス プロセスを停止することができます。
ConsoleLifetime
が使用されている場合は、次のシグナルをリッスンし、ホストの正常な停止を試みます。- アプリによって 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 処理でプロセスの正常なシャットダウンを試みていたため、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 シグナル ハンドラーからCancel = true
が返されます。これは、この POSIX シグナルが処理されたためです。 Main
実行コードではもう一度実行を開始し、ホストにStopAsync()
を指示し、その後、ホストされているサービスをすべて停止し、その他の停止イベントを発生させます。- 最後に、
WaitForShutdown
が終了し、アプリケーションのクリーンアップ コードを実行できるようにし、Main
メソッドを正常に終了できるようにします。
関連項目
- .NET での依存関係の挿入
- .NET でのログの記録
- .NET での構成
- .NET の Worker サービス
- ASP.NET Core の Web ホスト
- 汎用ホストのバグは、github.com/dotnet/extensions リポジトリに作成する必要があります