Generischer .NET-Host
In diesem Artikel erfahren Sie mehr über die verschiedenen Muster zum Konfigurieren und Erstellen eines generischen .NET-Hosts, der im NuGet-Paket Microsoft.Extensions.Hosting verfügbar ist. Der generische .NET-Host ist verantwortlich für das Starten der App und das Verwalten der Lebensdauer. Die Workerdienstvorlagen erstellen einen generischen .NET-Host HostApplicationBuilder. Der generische Host kann mit anderen Typen von .NET-Anwendungen verwendet werden, z. B. Konsolen-Apps.
Ein Host ist ein Objekt, das die Ressourcen und Lebensdauerfunktionen einer App kapselt, z. B.:
- Abhängigkeitsinjektion
- Protokollierung
- Konfiguration
- Herunterfahren der App
IHostedService
-Implementierungen
Beim Starten eines Hosts wird IHostedService.StartAsync für jede Implementierung von IHostedService aufgerufen, die in der Sammlung gehosteter Dienste des Dienstcontainers registriert ist. In einer Workerdienst-App werden bei allen IHostedService
-Implementierungen, die BackgroundService-Instanzen enthalten, die BackgroundService.ExecuteAsync-Methoden aufgerufen.
Der wichtigste Grund für das Einschließen aller unabhängigen Ressourcen der App in einem Objekt ist die Verwaltung der Lebensdauer: steuern von Start und ordnungsgemäßem Herunterfahren der App.
Einrichten eines Hosts
Der Host wird in der Regel per Code in der Program
-Klasse konfiguriert, erstellt und ausgeführt. Die Main
-Methode:
- Ruft eine CreateApplicationBuilder-Methode zum Erstellen und Konfigurieren eines Generatorobjekts auf.
- Ruft Build() zum Erstellen einer IHost-Instanz auf.
- Ruft die Run- oder die RunAsync-Methode für das Hostobjekt auf.
Die .NET-Workerdienstvorlagen generieren den folgenden Code zum Erstellen eines generischen Hosts:
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
Weitere Informationen zu Workerdiensten finden Sie unter Workerdienste in .NET.
Einstellungen für Hostgenerator
Die CreateApplicationBuilder-Methode:
- Legt das Inhaltsstammverzeichnis auf den Pfad fest, der von GetCurrentDirectory() zurückgegeben wird.
- Lädt Hostkonfiguration aus:
- Umgebungsvariablen mit dem Präfix
DOTNET_
. - Befehlszeilenargumenten
- Umgebungsvariablen mit dem Präfix
- Lädt App-Konfiguration aus:
- appsettings.json
- appsettings.{Environment}.json
- Geheimnis-Manager, wenn die App in der
Development
-Umgebung ausgeführt wird - Umgebungsvariablen.
- Befehlszeilenargumenten
- Fügt die folgenden Protokollierungsanbieter hinzu:
- Konsole
- Debuggen
- EventSource
- EventLog (nur bei Ausführung unter Windows)
- Aktiviert die Bereichsüberprüfung und die Abhängigkeitsüberprüfung, wenn es sich um eine
Development
-Umgebung handelt.
HostApplicationBuilder.Services ist eine Microsoft.Extensions.DependencyInjection.IServiceCollection-Instanz. Mit diesen Diensten wird ein IServiceProvider mit Abhängigkeitsinjektion zum Auflösen der registrierten Dienste erstellt.
Von Frameworks bereitgestellte Dienste
Wenn Sie entweder IHostBuilder.Build() oder HostApplicationBuilder.Build() aufrufen, werden die folgenden Dienste automatisch registriert:
Weitere szenariobasierte Host-Generatoren
Wenn Sie für das Web entwickeln oder eine verteilte Anwendung schreiben, müssen Sie möglicherweise einen anderen Host-Generator verwenden. Berücksichtigen Sie die folgende Liste zusätzlicher Host-Generatoren:
- DistributedApplicationBuilder: Ein Generator zum Erstellen verteilter Apps. Weitere Informationen finden Sie unter .NET Aspire.
- WebApplicationBuilder: Ein Generator für Webanwendungen und -dienste. Weitere Informationen finden Sie unter ASP.NET Core .
- WebHostBuilder: Ein Generator für
IWebHost
. Weitere Informationen finden Sie unter ASP.NET Core-Webhost.
IHostApplicationLifetime
Fügen Sie den IHostApplicationLifetime-Dienst durch Injektion in eine beliebige Klasse ein, um die Aufgaben nach dem Starten und die Aufgaben für ordnungsgemäßes Herunterfahren zu verarbeiten. Bei drei der Eigenschaften der Schnittstelle handelt es sich um Abbruchtokens, die zum Registrieren von Ereignishandlermethoden zum Starten und Beenden von Apps verwendet werden. Die Benutzeroberfläche umfasst auch eine StopApplication()-Methode.
Das folgende Beispiel zeigt eine IHostedService- und IHostedLifecycleService-Implementierung, die IHostApplicationLifetime
-Ereignisse registriert:
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.");
}
}
Die Workerdienstvorlage kann geändert werden, um die ExampleHostedService
-Implementierung hinzuzufügen:
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();
Die Anwendung schreibt dann die folgende Beispielausgabe:
// 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.
Die Ausgabe zeigt die Reihenfolge aller verschiedenen Lebenszyklusereignisse an:
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
Wenn die Anwendung beendet wird, z. B. mit Strg+C, werden die folgenden Ereignisse ausgelöst:
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
IHostLifetime
Die IHostLifetime-Implementierung steuert, wann der Host gestartet und beendet wird. Die zuletzt registrierte Implementierung wird verwendet. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
ist die IHostLifetime
-Standardimplementierung. Weitere Informationen zu den Lebensdauermechanismen des Herunterfahrens finden Sie unter Herunterfahren des Hosts.
Die IHostLifetime
-Schnittstelle macht eine IHostLifetime.WaitForStartAsync-Methode verfügbar, die am Anfang von IHost.StartAsync
aufgerufen wird. Auf den Abschluss wird gewartet, bevor der Vorgang fortsetzt wird. Dies kann verwendet werden, um den Start zu verzögern, bis dieser durch ein externes Ereignis initiiert wird.
Darüber hinaus macht die IHostLifetime
-Schnittstelle eine IHostLifetime.StopAsync-Methode verfügbar, die von IHost.StopAsync
aufgerufen wird, um anzugeben, dass der Host beendet wird und es an der Zeit ist, herunterzufahren.
IHostEnvironment
Fügt den IHostEnvironment-Dienst in eine Klasse ein, um Informationen über die folgenden Einstellungen abzurufen:
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
Darüber hinaus macht der IHostEnvironment
-Dienst die Möglichkeit verfügbar, die Umgebung mithilfe dieser Erweiterungsmethoden auszuwerten:
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
Konfiguration des Hosts
Die Hostkonfiguration wird zum Konfigurieren von Eigenschaften der IHostEnvironment-Implementierung verwendet.
Die Hostkonfiguration ist in der HostApplicationBuilderSettings.Configuration-Eigenschaft verfügbar, und die Umgebungsimplementierung ist in der IHostApplicationBuilder.Environment-Eigenschaft verfügbar. Um den Host zu konfigurieren, greifen Sie auf die Configuration
-Eigenschaft zu, und rufen Sie eine der verfügbaren Erweiterungsmethoden auf.
Sehen Sie sich zum Hinzufügen der Hostkonfiguration das folgende Beispiel an:
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();
Der vorangehende Code:
- Legt das Inhaltsstammverzeichnis auf den Pfad fest, der von GetCurrentDirectory() zurückgegeben wird.
- Lädt Hostkonfiguration aus:
- hostsettings.json.
- Umgebungsvariablen mit dem Präfix
PREFIX_
. - Befehlszeilenargumenten
App-Konfiguration
Die App-Konfiguration wird durch Aufrufen von ConfigureAppConfiguration für IHostApplicationBuilder erstellt. Die öffentlicheIHostApplicationBuilder.Configuration Eigenschaft ermöglicht es Consumern, mithilfe verfügbarer Erweiterungsmethoden aus der vorhandenen Konfiguration zu lesen oder Änderungen an der vorhandenen Konfiguration vorzunehmen.
Weitere Informationen finden Sie unter Konfiguration in .NET.
Herunterfahren des Hosts
Es gibt mehrere Möglichkeiten, einen gehosteten Prozess zu beenden. Meist kann ein gehosteter Prozess auf folgende Weise beendet werden:
- Wenn jemand nicht Run oder HostingAbstractionsHostExtensions.WaitForShutdown aufruft und die App normalerweise mit dem Abschluss von
Main
beendet wird. - Wenn die App abstürzt.
- Wenn die Anwendung mit SIGKILL (oder STRG+Z) zwangsweise heruntergefahren wird.
Der Hostingcode ist nicht für die Verarbeitung dieser Szenarien verantwortlich. Der oder die Besitzer*in des Prozesses muss sie wie jede andere Anwendung behandeln. Es gibt mehrere weitere Möglichkeiten, einen gehosteten Dienstprozess zu beenden:
- Wenn
ConsoleLifetime
verwendet wird (UseConsoleLifetime), lauscht es auf die folgenden Signale und versucht, den Host ordnungsgemäß zu beenden. - Wenn die App Environment.Exit aufruft.
Diese Szenarien werden von der integrierten Hostinglogik verarbeitet, insbesondere von der ConsoleLifetime
-Klasse. ConsoleLifetime
versucht, das „Herunterfahren“ zu verarbeiten, signalisiert SIGINT, SIGQUIT und SIGTERM, um ein ordnungsgemäßes Beenden der Anwendung zu ermöglichen.
Vor .NET 6 gab es keine Möglichkeit für .NET-Code, SIGTERM ordnungsgemäß zu verarbeiten. Um diese Einschränkung zu umgehen, abonnierte ConsoleLifetime
System.AppDomain.ProcessExit. Wenn ProcessExit
ausgelöst wurde, signalisierte ConsoleLifetime
dem Host, anzuhalten und den ProcessExit
-Thread zu blockieren, darauf wartend, dass der Host anhält.
Der Handler zum Beenden des Prozesses könnte den Bereinigungscode in der Anwendung ausführen, z. B. IHost.StopAsync und den Code nach HostingAbstractionsHostExtensions.Run in der Main
-Methode.
Es gab jedoch einige Probleme bei diesem Ansatz, da SIGTERM nicht die einzige Ursache für das Auslösen von ProcessExit
war. SIGTERM wird auch ausgelöst, wenn App-Code Environment.Exit
aufruft. Environment.Exit
ist keine gute Möglichkeit, einen Prozess im Microsoft.Extensions.Hosting
-App-Modell herunterzufahren. Es löst das ProcessExit
-Ereignis aus und beendet den Prozess. Das Ende der Main
-Methode wird nicht ausgeführt. Hintergrund- und Vordergrundthreads werden beendet, und finally
-Blöcke werden nicht ausgeführt.
Da ConsoleLifetime
beim Warten auf das Herunterfahren des Hosts ProcessExit
blockierte, führte dieses Verhalten zu Deadlocks, da Environment.Exit
beim Warten auf den Aufruf von ProcessExit
ebenfalls zu einer Blockade führt. Da die SIGTERM-Behandlung außerdem versuchte, den Prozess ordnungsgemäß herunterzufahren, setzte ConsoleLifetime
den ExitCode auf 0
, was den an Environment.Exit
übergebenen Exitcode des Benutzers beeinträchtigte.
In .NET 6 werden POSIX-Signale unterstützt und verarbeitet. ConsoleLifetime
behandelt SIGTERM ordnungsgemäß und wird nicht mehr einbezogen, wenn Environment.Exit
aufgerufen wird.
Tipp
Für .NET 6+ verfügt ConsoleLifetime
nicht mehr über Logik zur Behandlung des Szenarios Environment.Exit
. Apps, die Environment.Exit
aufrufen und eine Bereinigungslogik ausführen müssen, können selbst ProcessExit
abonnieren. Beim Hosting wird in diesen Szenarien nicht mehr versucht, den Host ordnungsgemäß zu beenden.
Wenn Ihre Anwendung Hosting verwendet und Sie den Host ordnungsgemäß beenden möchten, können Sie IHostApplicationLifetime.StopApplication anstelle von Environment.Exit
aufrufen.
Prozess zum Herunterfahren des Hostings
Das folgende Sequenzdiagramm zeigt, wie die Signale intern im Hostingcode behandelt werden. Die meisten Benutzer müssen diesen Vorgang nicht verstehen. Aber Entwickler*innen, bei denen ein umfassendes Verständnis vorausgesetzt werden muss, kann diese Vorstellung den Einstieg vereinfachen.
Wenn ein Benutzer nach dem Start des Hosts Run
oder WaitForShutdown
aufruft, wird ein Handler für IApplicationLifetime.ApplicationStopping registriert. Die Ausführung wird in WaitForShutdown
angehalten, bis das ApplicationStopping
-Ereignis ausgelöst wird. Die Rückgabe der Main
-Methode erfolgt nicht sofort, und die App wird weiter ausgeführt, bis eine Rückgabe von Run
oder WaitForShutdown
erfolgt.
Wenn ein Signal an den Prozess gesendet wird, wird die folgende Sequenz initiiert:
- Die Steuerung fließt von
ConsoleLifetime
zuApplicationLifetime
, um dasApplicationStopping
-Ereignis auszulösen. Dies signalisiertWaitForShutdownAsync
, denMain
-Ausführungscode freizugeben. In der Zwischenzeit gibt der POSIX-SignalhandlerCancel = true
zurück, da dieses POSIX-Signal verarbeitet wurde. - Der
Main
-Ausführungscode beginnt erneut mit der Ausführung und führt einStopAsync()
für den Host aus, wodurch wiederum alle gehosteten Dienste beendet und alle anderen beendeten Ereignisse ausgelöst werden. - Schließlich wird
WaitForShutdown
beendet, sodass Anwendungsbereinigungscode ausgeführt und dieMain
-Methode ordnungsgemäß beendet werden kann.
Herunterfahren des Hosts in Webserverszenarien
Es gibt verschiedene andere gängige Szenarien, in denen das ordnungsgemäße Herunterfahren in Kestrel für die Protokolle HTTP/1.1 und HTTP/2 funktioniert. Sie können dies in verschiedenen Umgebungen mit einem Lastenausgleich konfigurieren, um den Datenverkehr reibungslos auszugleichen. Die Webserverkonfiguration überschreitet den Rahmen dieses Artikels, weitere Informationen finden Sie in der Dokumentation zu den Konfigurationsoptionen für ASP.NET Core Kestrel-Webserver.
Wenn der Host ein Signal zum Herunterfahren empfängt (z. B. CTL+C oder StopAsync
), benachrichtigt er die Anwendung, indem er ApplicationStopping signalisiert. Sie sollten dieses Ereignis abonnieren, wenn Sie über Vorgänge mit langer Ausführungsdauer verfügen, die ordnungsgemäß abgeschlossen werden müssen.
Als Nächstes ruft der Host IServer.StopAsync mit einem Timeout für das Herunterfahren auf, das Sie konfigurieren können (Standardwert: 30 s). Kestrel (und Http.Sys) schließen ihre Portbindungen und beenden die Annahme neuer Verbindungen. Außerdem weisen sie die aktuellen Verbindungen an, die Verarbeitung neuer Anforderungen zu beenden. Für HTTP/2 und HTTP/3 wird eine vorläufige GOAWAY
-Nachricht an den Client gesendet. Für HTTP/1.1 wird die Verbindungsschleife beendet, da Anforderungen nacheinander verarbeitet werden. IIS verhält sich anders, indem es neue Anforderungen mit dem Statuscode 503 ablehnt.
Die aktiven Anforderungen müssen bis zum Abschluss des Timeouts für das Herunterfahren abgeschlossen werden. Wenn alle vor dem Timeout abgeschlossen sind, gibt der Server die Steuerung früher an den Host zurück. Wenn das Timeout abläuft, wird das Unterbrechen der ausstehenden Verbindungen und Anforderungen erzwungen, was zu Fehlern in den Protokollen und auf Clients führen kann.
Überlegungen zu Load Balancer
Führen Sie die folgenden Schritte aus, um einen reibungslosen Übergang von Clients zu einem neuen Ziel zu gewährleisten, wenn Sie einen Lastenausgleich verwenden:
- Aktivieren Sie die neue Instanz, und beginnen Sie mit dem Ausgleich des Datenverkehrs (möglicherweise verfügen Sie zu Skalierungszwecken bereits über mehrere Instanzen).
- Deaktivieren oder entfernen Sie die alte Instanz in der Konfiguration für den Lastenausgleich, damit sie nicht mehr neuen Datenverkehr empfängt.
- Benachrichtigen Sie die alte Instanz über das Herunterfahren.
- Warten Sie, bis der Ausgleich oder das Timeout erfolgt.
Siehe auch
- Abhängigkeitsinjektion in .NET
- Protokollierung in .NET
- Konfiguration in .NET
- Workerdienste in .NET
- ASP.NET Core-Webhost
- Konfiguration des ASP.NET Core-Kestrel-Webservers
- Fehler bei generischen Hosts sollten im Repository github.com/dotnet/runtime erstellt werden.