.NET 泛型主機

您將透過本文了解設定及組建Microsoft.Extensions.Hosting NuGet 套件中可用的 .NET 泛型主機的各種模式。 .NET 泛型主機會負責應用程式啟動和存留期管理。 背景工作服務範本會建立 .NET 泛型主機 HostApplicationBuilder。 泛型主機可以與其他型別的 .NET 應用程式搭配使用,例如主機應用程式。

「主機」是封裝應用程式資源和存留期功能的物件,例如:

  • 相依性插入 (DI)
  • 記錄
  • 組態
  • 應用程式關機
  • IHostedService 實作

主機啟動時,會在服務容器託管服務集合中所註冊 IHostedService 的每個實作上呼叫 IHostedService.StartAsync。 在背景工作服務應用程式中,包含 BackgroundService 執行個體的所有 IHostedService 實作都會呼叫其 BackgroundService.ExecuteAsync 方法。

在單一物件中包含所有應用程式相互依存資源的主要理由便是生命週期管理:控制應用程式的啟動及順利關機。

設定主機

主機通常由 Program 類別的程式碼來設定、建置並執行。 Main 方法:

.NET 背景工作服務範本會產生下列程式碼來建立泛型主機:

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
    • EventLog (僅當在 Windows 上執行時)
  • 環境為 Development 時,會啟用範圍驗證和相依性驗證

HostApplicationBuilder.Services 是個 Microsoft.Extensions.DependencyInjection.IServiceCollection 執行個體。 這些服務可用來組建與相依性插入搭配使用的 IServiceProvider,以解析已註冊的服務。

架構提供的服務

當您呼叫 IHostBuilder.Build()HostApplicationBuilder.Build() 時,會自動登錄下列服務:

IHostApplicationLifetime

IHostApplicationLifetime 服務插入至任何類別,以處理啟動後和順利關機工作。 介面上的三個屬性是用於註冊應用程式啟動和應用程式關閉事件處理程序方法的取消語彙基元。 介面也包括 StopApplication() 方法。

下列範例是 IHostedService 註冊事件的 和 IHostedLifecycleService 實作 IHostApplicationLifetime

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.

輸出會顯示所有各種生命週期事件的順序:

  1. IHostedLifecycleService.StartingAsync
  2. IHostedService.StartAsync
  3. IHostedLifecycleService.StartedAsync
  4. IHostApplicationLifetime.ApplicationStarted

當應用程式停止時,例如使用 Ctrl+C,就會引發下列事件:

  1. IHostApplicationLifetime.ApplicationStopping
  2. IHostedLifecycleService.StoppingAsync
  3. IHostedService.StopAsync
  4. IHostedLifecycleService.StoppedAsync
  5. IHostApplicationLifetime.ApplicationStopped

IHostLifetime

IHostLifetime 實作會控制主機啟動及停止的時機。 會使用最後一個註冊的實作。 Microsoft.Extensions.Hosting.Internal.ConsoleLifetime 是預設的 IHostLifetime 實作。 如需關機存留期機制的詳細資訊,請參閱主機關機

介面 IHostLifetime 會公開方法 IHostLifetime.WaitForStartAsync ,其會在開頭 IHost.StartAsync 呼叫,其會等到它完成再繼續為止。 這可用來將啟動延遲到外部事件發出訊號為止。

此外, IHostLifetime 介面會公開方法 IHostLifetime.StopAsync ,這個方法會從 IHost.StopAsync 呼叫,以指出主機正在停止,而且是時候關閉。

IHostEnvironment

IHostEnvironment 服務插入至類別,以取得下列設定的相關資訊:

此外,IHostEnvironment 服務會透過這些擴充方法的協助,公開評估環境的能力:

主機組態

主機設定用來設定 IHostEnvironment 實作的屬性。

主機設定可在 IHostApplicationBuilder.Configuration 屬性中使用,而環境實作可在 IHostApplicationBuilder.Environment 屬性中使用。 若要設定主機,請存取 Configuration 屬性並呼叫任何可用的擴充方法。

若要新增主機設定,請考慮下列範例:

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Environment.ContentRootPath = Directory.GetCurrentDirectory();
builder.Configuration.AddJsonFile("hostsettings.json", optional: true);
builder.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
builder.Configuration.AddCommandLine(args);

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

上述 程式碼:

  • 設定 GetCurrentDirectory() 所傳回路徑的內容根目錄。
  • 從下列項目載入主機組態:
    • hostsettings.json
    • 前面加上 PREFIX_ 的環境變數。
    • 命令列引數。

應用程式設定

應用程式設定的建立方式是在 IHostApplicationBuilder 上呼叫 ConfigureAppConfiguration。 公開 IHostApplicationBuilder.Configuration 屬性可讓取用者使用可用的擴充方法來讀取或變更現有的設定。

如需詳細資訊,請參閱 .NET 中的設定

主機關機

有數種方式可以停止託管的處理程序。 託管的處理程序最常透過下列方式停止:

裝載程式碼不負責處理這些案例。 處理程序擁有者需要使用與任何其他應用程式相同的方式來進行處理。 有數種其他方式可以停止託管的服務處理程序:

  • 如果使用 ConsoleLifetime (UseConsoleLifetime),它會留意下列訊號,並嘗試正常停止主機。
    • SIGINT (或 CTRL+C)。
    • SIGQUIT (或 CTRL+BREAK (Windows 上)、CTRL+\ (Unix 上))。
    • SIGTERM (由其他應用程式傳送,例如 docker stop)。
  • 如果應用程式呼叫 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

裝載關機程序

下列序列圖顯示如何在裝載程式碼中內部處理訊號。 大部分的使用者不需要瞭解此程序。 但針對需要深入了解的開發人員,好的視覺物件可能有助於您開始使用。

啟動主機之後,使用者呼叫 RunWaitForShutdown 時,會註冊 IApplicationLifetime.ApplicationStopping 的處理常式。 執行會在 WaitForShutdown 中暫停,並等候引發 ApplicationStopping 事件。 不會立即傳回 Main 方法,而且應用程式會持續執行,直到 RunWaitForShutdown 傳回。

將訊號傳送至程序時,會起始下列序列:

裝載關機順序圖表。

  1. 控制項會從 ConsoleLifetime 流向 ApplicationLifetime 以引發 ApplicationStopping 事件。 這會發出 WaitForShutdownAsync 訊號以解除封鎖 Main 執行程式碼。 同時,由於已處理 POSIX 訊號,會使用 Cancel = true 傳回 POSIX 訊號處理常式。
  2. Main 執行程式碼會再次開始執行,並告知主機 StopAsync(),接著停止所有託管服務,並引發任何其他已停止事件。
  3. 最後,WaitForShutdown 會結束,並允許任何應用程式清除程式碼執行,以及允許 Main 方法正常結束。

網路伺服器案例中的主機關機

還有多種其他常見的案例可以在 Kestrel 中正常關閉 HTTP/1.1 和 HTTP/2 通訊協定的工作,以及您可以如何使用負載平衡器在不同的環境中進行設定,以順利疏導流量。 雖然網路伺服器設定超出本文的範圍,但您可以在 ASP.NET Core Kestrel 網路伺服器的設定選項 文件中找到詳細資訊。

當主機收到關機訊號 (例如 CTL+CStopAsync),它會透過發出訊號 ApplicationStopping 來通知應用程式。 如果您有任何需要正常完成的長時間執行作業,您應該訂閱此事件。

接下來,主機會使用您可以設定的關機逾時呼叫 IServer.StopAsync (預設為 30 秒)。 Kestrel (和 Http.Sys) 會關閉其連接埠繫結,並停止接受新的連線。 它們也會告訴目前的連線停止處理新的要求。 針對 HTTP/2 和 HTTP/3,會將初步 GOAWAY 訊息傳送至用戶端。 針對 HTTP/1.1,它們會停止連線循環,因為要求會依序處理。 透過拒絕具有 503 狀態代碼的新要求,IIS 的行為會有所不同。

作用中的要求必須等到關機逾時才能完成。 如果它們都在逾時之前完成,伺服器就會更快將控制權傳回主機。 如果逾時過期,擱置的連線和要求會強制中止,這可能會導致記錄和客戶端發生錯誤。

負載平衡器考量

若要確保用戶端在使用負載平衡器時順暢地轉換到新的目的地,您可以遵循下列步驟:

  • 啟動新的執行個體,並開始平衡其流量 (您可能已經有數個用於縮放目的執行個體)。
  • 停用或移除負載平衡器設定中的舊執行個體,使其停止接收新的流量。
  • 向舊執行個體發出關閉的訊號。
  • 等候它清空或逾時。

另請參閱