.NET 泛型主機
您將透過本文了解設定及組建Microsoft.Extensions.Hosting NuGet 套件中可用的 .NET 泛型主機的各種模式。 .NET 泛型主機會負責應用程式啟動和存留期管理。 背景工作服務範本會建立 .NET 泛型主機 HostApplicationBuilder。 泛型主機可以與其他型別的 .NET 應用程式搭配使用,例如主機應用程式。
「主機」是封裝應用程式資源和存留期功能的物件,例如:
- 相依性插入 (DI)
- 記錄
- 組態
- 應用程式關機
IHostedService
實作
主機啟動時,會在服務容器託管服務集合中所註冊 IHostedService 的每個實作上呼叫 IHostedService.StartAsync。 在背景工作服務應用程式中,包含 BackgroundService 執行個體的所有 IHostedService
實作都會呼叫其 BackgroundService.ExecuteAsync 方法。
在單一物件中包含所有應用程式相互依存資源的主要理由便是生命週期管理:控制應用程式的啟動及順利關機。
設定主機
主機通常由 Program
類別的程式碼來設定、建置並執行。 Main
方法:
- 呼叫 CreateApplicationBuilder 方法來建立及設定建立器物件。
- 呼叫 Build() 以建立 IHost 執行個體。
- 在主機物件上呼叫 Run 或 RunAsync 方法。
.NET 背景工作服務範本會產生下列程式碼來建立泛型主機:
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
如需詳細資訊,請參閱 .NET 中的背景工作角色服務。
主機建立器設定
- 設定 GetCurrentDirectory() 所傳回路徑的內容根目錄。
- 從下列項目載入主機組態:
- 前面加上
DOTNET_
的環境變數。 - 命令列引數。
- 前面加上
- 從下列項目載入應用程式組態:
- appsettings.json。
- appsettings.{Environment}.json
- 應用程式在
Development
環境中執行時的祕密管理員。 - 環境變數。
- 命令列引數。
- 新增下列記錄提供者:
- 主控台
- 偵錯
- EventSource
- EventLog (僅當在 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 服務插入至任何類別,以處理啟動後和順利關機工作。 介面上的三個屬性是用於註冊應用程式啟動和應用程式關閉事件處理程序方法的取消語彙基元。 介面也包括 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
實作:
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。 公開 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
的唯一方法。 當應用程式程式碼呼叫 Environment.Exit
時,也會引發SIGTERM。 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
。 在此案例中,裝載將不會再嘗試正常停止主機。
如果您的應用程式使用裝載,而且您想要正常停止主機,則可以呼叫 IHostApplicationLifetime.StopApplication,而不是 Environment.Exit
。
裝載關機程序
下列序列圖顯示如何在裝載程式碼中內部處理訊號。 大部分的使用者不需要瞭解此程序。 但針對需要深入了解的開發人員,好的視覺物件可能有助於您開始使用。
啟動主機之後,使用者呼叫 Run
或 WaitForShutdown
時,會註冊 IApplicationLifetime.ApplicationStopping 的處理常式。 執行會在 WaitForShutdown
中暫停,並等候引發 ApplicationStopping
事件。 不會立即傳回 Main
方法,而且應用程式會持續執行,直到 Run
或 WaitForShutdown
傳回。
將訊號傳送至程序時,會起始下列序列:
- 控制項會從
ConsoleLifetime
流向ApplicationLifetime
以引發ApplicationStopping
事件。 這會發出WaitForShutdownAsync
訊號以解除封鎖Main
執行程式碼。 同時,由於已處理 POSIX 訊號,會使用Cancel = true
傳回 POSIX 訊號處理常式。 Main
執行程式碼會再次開始執行,並告知主機StopAsync()
,接著停止所有託管服務,並引發任何其他已停止事件。- 最後,
WaitForShutdown
會結束,並允許任何應用程式清除程式碼執行,以及允許Main
方法正常結束。
網路伺服器案例中的主機關機
還有多種其他常見的案例可以在 Kestrel 中正常關閉 HTTP/1.1 和 HTTP/2 通訊協定的工作,以及您可以如何使用負載平衡器在不同的環境中進行設定,以順利疏導流量。 雖然網路伺服器設定超出本文的範圍,但您可以在 ASP.NET Core Kestrel 網路伺服器的設定選項 文件中找到詳細資訊。
當主機收到關機訊號 (例如 CTL+C 或 StopAsync
),它會透過發出訊號 ApplicationStopping 來通知應用程式。 如果您有任何需要正常完成的長時間執行作業,您應該訂閱此事件。
接下來,主機會使用您可以設定的關機逾時呼叫 IServer.StopAsync (預設為 30 秒)。 Kestrel (和 Http.Sys) 會關閉其連接埠繫結,並停止接受新的連線。 它們也會告訴目前的連線停止處理新的要求。 針對 HTTP/2 和 HTTP/3,會將初步 GOAWAY
訊息傳送至用戶端。 針對 HTTP/1.1,它們會停止連線循環,因為要求會依序處理。 透過拒絕具有 503 狀態代碼的新要求,IIS 的行為會有所不同。
作用中的要求必須等到關機逾時才能完成。 如果它們都在逾時之前完成,伺服器就會更快將控制權傳回主機。 如果逾時過期,擱置的連線和要求會強制中止,這可能會導致記錄和客戶端發生錯誤。
負載平衡器考量
若要確保用戶端在使用負載平衡器時順暢地轉換到新的目的地,您可以遵循下列步驟:
- 啟動新的執行個體,並開始平衡其流量 (您可能已經有數個用於縮放目的執行個體)。
- 停用或移除負載平衡器設定中的舊執行個體,使其停止接收新的流量。
- 向舊執行個體發出關閉的訊號。
- 等候它清空或逾時。