Allmän .NET-värd

I den här artikeln får du lära dig mer om de olika mönstren för att konfigurera och skapa en .NET Generic Host som är tillgänglig i NuGet-paketet Microsoft.Extensions.Hosting . .NET Generic Host ansvarar för appstart och livslängdshantering. Arbetstjänstmallarna skapar en allmän .NET-värd, HostApplicationBuilder. Den allmänna värden kan användas med andra typer av .NET-program, till exempel konsolappar.

En värd är ett objekt som kapslar in en apps resurser och livslängdsfunktioner, till exempel:

  • Beroendeinmatning (DI)
  • Loggning
  • Konfiguration
  • Appavstängning
  • IHostedService Implementeringar

När en värd startar anropas IHostedService.StartAsync varje implementering av IHostedService registrerad i tjänstcontainerns samling värdbaserade tjänster. I en arbetstjänstapp anropas BackgroundService.ExecuteAsync alla IHostedService implementeringar som innehåller BackgroundService instanser.

Den främsta orsaken till att inkludera alla appens beroende resurser i ett objekt är livslängdshantering: kontroll över appstart och graciös avstängning.

Konfigurera en värd

Värden är vanligtvis konfigurerad, byggd och kör med kod i Program klassen. Metoden Main :

Mallarna för .NET Worker Service genererar följande kod för att skapa en allmän värd:

using Example.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

Mer information om Worker Services finns i Worker Services i .NET.

Inställningar för värdbyggare

Metoden CreateApplicationBuilder :

  • Anger innehållsroten till sökvägen som returneras av GetCurrentDirectory().
  • Läser in värdkonfiguration från :
    • Miljövariabler med prefixet DOTNET_.
    • Kommandoradsargument.
  • Läser in appkonfiguration från:
    • appsettings.json.
    • appsettings. {Environment}.json.
    • Secret Manager när appen körs i Development miljön.
    • Miljövariabler.
    • Kommandoradsargument.
  • Lägger till följande loggningsproviders:
    • Konsol
    • Felsöka
    • EventSource
    • EventLog (endast när du kör på Windows)
  • Aktiverar omfångsverifiering och beroendeverifiering när miljön är Development.

HostApplicationBuilder.Services Är en Microsoft.Extensions.DependencyInjection.IServiceCollection instans. Dessa tjänster används för att skapa en IServiceProvider som används med beroendeinmatning för att lösa de registrerade tjänsterna.

Ramverksbaserade tjänster

När du anropar antingen IHostBuilder.Build() eller HostApplicationBuilder.Build()registreras följande tjänster automatiskt:

IHostApplicationLifetime

IHostApplicationLifetime Mata in tjänsten i valfri klass för att hantera uppgifter efter start och graciös avstängning. Tre egenskaper i gränssnittet är annulleringstoken som används för att registrera metoder för att starta och stoppa apphändelser. Gränssnittet innehåller också en StopApplication() metod.

Följande exempel är en IHostedService implementering som IHostedLifecycleService registrerar IHostApplicationLifetime händelser:

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.");
    }
}

Arbetstjänstmallen kan ändras för att lägga till implementeringen 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();

Programmet skulle skriva följande exempelutdata:

// 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.

Utdata visar ordningen på alla olika livscykelhändelser:

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

När programmet stoppas, till exempel med Ctrl+C, utlöses följande händelser:

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

IHostLifetime

Implementeringen IHostLifetime styr när värden startar och när den stoppas. Den senast registrerade implementeringen används. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime är standardimplementeringen IHostLifetime . Mer information om livslängdsmekaniken för avstängning finns i Avstängning av värd.

Gränssnittet IHostLifetime exponerar en IHostLifetime.WaitForStartAsync metod som anropas i början av IHost.StartAsync vilken väntar tills den är klar innan den fortsätter. Detta kan användas för att fördröja starten tills den signaleras av en extern händelse.

Dessutom IHostLifetime exponerar gränssnittet en IHostLifetime.StopAsync metod som anropas från IHost.StopAsync för att indikera att värden stoppas och att det är dags att stänga av.

IHostEnvironment

Mata in tjänsten IHostEnvironment i en klass för att få information om följande inställningar:

Dessutom IHostEnvironment visar tjänsten möjligheten att utvärdera miljön med hjälp av dessa tilläggsmetoder:

Värdkonfiguration

Värdkonfiguration används för att konfigurera egenskaperna för IHostEnvironment-implementeringen .

Värdkonfigurationen är tillgänglig i IHostApplicationBuilder.Configuration egenskapen och miljöimplementeringen är tillgänglig i IHostApplicationBuilder.Environment egenskapen. Om du vill konfigurera värden öppnar du Configuration egenskapen och anropar någon av de tillgängliga tilläggsmetoderna.

Tänk på följande exempel för att lägga till värdkonfiguration:

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();

Koden ovan:

  • Anger innehållsroten till sökvägen som returneras av GetCurrentDirectory().
  • Läser in värdkonfiguration från:
    • hostsettings.json.
    • Miljövariabler med prefixet PREFIX_.
    • Kommandoradsargument.

Appkonfiguration

Appkonfigurationen skapas genom att anropa ConfigureAppConfiguration på en IHostApplicationBuilder. Med den offentligaIHostApplicationBuilder.Configuration egenskapen kan användarna läsa från eller göra ändringar i den befintliga konfigurationen med hjälp av tillgängliga tilläggsmetoder.

Mer information finns i Konfiguration i .NET.

Avstängning av värd

Det finns flera sätt på vilka en värdbaserad process stoppas. Oftast kan en värdbaserad process stoppas på följande sätt:

Värdkoden ansvarar inte för att hantera dessa scenarier. Processens ägare måste hantera dem på samma sätt som andra appar. Det finns flera andra sätt på vilka en värdbaserad tjänstprocess kan stoppas:

  • Om ConsoleLifetime används (UseConsoleLifetime) lyssnar den efter följande signaler och försöker stoppa värden på ett smidigt sätt.
    • SIGINT (eller CTRL+C).
    • SIGQUIT (eller CTRL+BREAK på Windows, CTRL+\ på Unix).
    • SIGTERM (skickas av andra appar, till exempel docker stop).
  • Om appen anropar Environment.Exit.

Den inbyggda värdlogiken hanterar dessa scenarier, särskilt ConsoleLifetime klassen. ConsoleLifetime försöker hantera "avstängningssignalerna" SIGINT, SIGQUIT och SIGTERM för att möjliggöra en korrekt avslutning av programmet.

Före .NET 6 fanns det inget sätt för .NET-kod att hantera SIGTERM korrekt. Om du vill kringgå den här begränsningen ConsoleLifetime prenumererar du på System.AppDomain.ProcessExit. När ProcessExit aktiverades ConsoleLifetime signalerades värden att stoppa och blockera tråden ProcessExit i väntan på att värden skulle stoppas.

Processavslutshanteraren skulle göra det möjligt för rensningskoden i programmet att köras, IHost.StopAsync till exempel och koda efter HostingAbstractionsHostExtensions.Run i Main -metoden.

Det fanns dock andra problem med den här metoden eftersom SIGTERM inte var det enda sättet ProcessExit som togs upp. SIGTERM aktiveras också när appkod anropar Environment.Exit. Environment.Exit är inte ett smidigt sätt att stänga av en process i Microsoft.Extensions.Hosting appmodellen. Händelsen genereras ProcessExit och processen avslutas. Slutet av Main metoden körs inte. Bakgrunds- och förgrundstrådar avslutas och finally block körs inte .

Eftersom ConsoleLifetime det blockerades ProcessExit i väntan på att värden skulle stängas av ledde det här beteendet till dödlägen från Environment.Exit block som väntade på anropet till ProcessExit. Eftersom SIGTERM-hanteringen försökte stänga processen på ett korrekt sätt skulle ConsoleLifetime den dessutom ange ExitCode till 0, som klonade användarens slutkod som skickades till Environment.Exit.

I .NET 6 stöds och hanteras POSIX-signaler . Hanterar ConsoleLifetime SIGTERM på ett korrekt sätt och engageras inte längre när Environment.Exit anropas.

Dricks

För .NET 6+ ConsoleLifetime har inte längre logik för att hantera scenariot Environment.Exit. Appar som anropar Environment.Exit och behöver utföra rensningslogik kan prenumerera ProcessExit på sig själva. Värdtjänster försöker inte längre att på ett smidigt sätt stoppa värden i dessa scenarier.

Om ditt program använder värd och du vill stoppa värden på ett smidigt sätt kan du anropa IHostApplicationLifetime.StopApplication i stället Environment.Exitför .

Värdavstängningsprocess

Följande sekvensdiagram visar hur signalerna hanteras internt i värdkoden. De flesta användare behöver inte förstå den här processen. Men för utvecklare som behöver en djup förståelse kan ett bra visuellt objekt hjälpa dig att komma igång.

När värden har startats, när en användare anropar Run eller WaitForShutdown, registreras en hanterare för IApplicationLifetime.ApplicationStopping. Körningen pausas i WaitForShutdownoch väntar ApplicationStopping på att händelsen ska aktiveras. Metoden Main returnerar inte direkt och appen fortsätter att köras tills Run eller WaitForShutdown returnerar.

När en signal skickas till processen initieras följande sekvens:

Värd för sekvensdiagram för avstängning.

  1. Kontrollen flödar från ConsoleLifetime till för ApplicationLifetime att skapa ApplicationStopping händelsen. Detta signalerar WaitForShutdownAsync att avblockera körningskoden Main . Under tiden returnerar POSIX-signalhanteraren med Cancel = true eftersom POSIX-signalen har hanterats.
  2. Körningskoden Main börjar köras igen och instruerar värden till StopAsync(), vilket i sin tur stoppar alla värdbaserade tjänster och genererar andra stoppade händelser.
  3. WaitForShutdown Slutligen avslutas, vilket gör att all programrensningskod kan köras och Main att metoden avslutas korrekt.

Värdavstängning i webbserverscenarier

Det finns olika andra vanliga scenarier där graciös avstängning fungerar i Kestrel för både HTTP/1.1- och HTTP/2-protokoll, och hur du kan konfigurera den i olika miljöer med en lastbalanserare för att tömma trafiken smidigt. Även om webbserverkonfigurationen ligger utanför omfånget för den här artikeln kan du hitta mer information om konfigurera alternativ för ASP.NET Core Kestrel-webbserverdokumentation .

När värden tar emot en avstängningssignal (till exempel CTL+C eller StopAsync), meddelar den programmet genom att signalera .ApplicationStopping Du bör prenumerera på den här händelsen om du har några långvariga åtgärder som måste slutföras korrekt.

Därefter anropar IServer.StopAsync värden med en tidsgräns för avstängning som du kan konfigurera (standard 30-talet). Kestrel (och Http.Sys) stänger sina portbindningar och slutar acceptera nya anslutningar. De uppmanar också de aktuella anslutningarna att sluta bearbeta nya begäranden. För HTTP/2 och HTTP/3 skickas ett preliminärt GOAWAY meddelande till klienten. För HTTP/1.1 stoppar de anslutningsloopen eftersom begäranden bearbetas i ordning. IIS fungerar annorlunda genom att avvisa nya begäranden med statuskoden 503.

De aktiva begärandena har tills tidsgränsen för avstängningen har slutförts. Om alla är klara före tidsgränsen returnerar servern kontrollen till värden tidigare. Om tidsgränsen upphör att gälla avbryts väntande anslutningar och begäranden med kraft, vilket kan orsaka fel i loggarna och till klienterna.

Överväganden för lastbalanserare

För att säkerställa en smidig övergång av klienter till ett nytt mål när du arbetar med en lastbalanserare kan du följa dessa steg:

  • Ta upp den nya instansen och börja balansera trafik till den (du kanske redan har flera instanser i skalningssyfte).
  • Inaktivera eller ta bort den gamla instansen i lastbalanserarens konfiguration så att den slutar ta emot ny trafik.
  • Signalera att den gamla instansen ska stängas av.
  • Vänta tills den töms eller överskrider tidsgränsen.

Se även