Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье вы узнаете о различных шаблонах настройки и создания универсального узла .NET, доступного в пакете NuGet Microsoft.Extensions.Hosting . Узел «Generic Host» в .NET отвечает за запуск приложений и управление временем их существования. Шаблоны рабочей службы создают универсальный узел .NET HostApplicationBuilder. Этот универсальный хост можно использовать в сочетании с другими типами приложений .NET, такими как консольные приложения.
Хост — это объект, который инкапсулирует все ресурсы приложения и функциональные возможности времени существования, такие как:
- Внедрение зависимостей
- Ведение журнала
- Настройка
- завершение работы приложения;
- Реализации
IHostedService
После запуска узла он вызывает IHostedService.StartAsync в каждой реализации IHostedService, зарегистрированной в контейнере размещенных служб. В приложении службы рабочих процессов для всех реализаций IHostedService, которые содержат экземпляры BackgroundService, вызываются соответствующие методы BackgroundService.ExecuteAsync.
Основной причиной включения всех взаимозависимых ресурсов приложения в один объект является управление жизненным циклом: контроль запуска и корректного завершения работы приложения.
Опции конфигуратора хоста
.NET предоставляет два подхода к настройке и созданию универсального узла:
IHostApplicationBuilder (
Host.CreateApplicationBuilder): В .NET 6 этот подход использует линейный стиль конфигурации на основе свойств. Службы, конфигурация и ведение журнала настраиваются путем прямого доступа к свойствам объекта построителя (например,builder.Services).builder.ConfigurationЭтот подход рекомендуется для новых проектов и используется по умолчанию в текущих шаблонах .NET.IHostBuilder(
Host.CreateDefaultBuilder). Это традиционный подход на основе обратного вызова, где конфигурация выполняется с помощью методов расширения в цепочке (например,ConfigureServices).ConfigureAppConfigurationХотя и полностью поддерживается, этот устаревший подход лучше всего подходит для обеспечения совместимости с существующими базами кода.
Оба подхода обеспечивают одинаковые основные функции и поведение по умолчанию. Выберите IHostApplicationBuilder для новых проектов, чтобы соответствовать современным шаблонам .NET и для упрощения кода конфигурации. Используйте IHostBuilder при обслуживании существующих приложений или когда сторонние библиотеки требуют шаблона обратного вызова.
Настройка узла
Хост обычно настраивается, собирается и выполняется кодом класса 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.
Параметры хост-билдера
Метод CreateApplicationBuilder:
- В качестве корня содержимого задает путь, возвращенный методом GetCurrentDirectory().
- Загружает конфигурацию узла из:
- Переменные среды с префиксом
DOTNET_. - аргументы командной строки.
- Переменные среды с префиксом
- Загружает конфигурацию приложения из:
- appsettings.json;
- appsettings.{Environment}.json;
- Менеджер секретов, когда приложение запускается в
Developmentсреде. - переменные окружения.
- аргументы командной строки.
- Добавляет следующие поставщики ведения журнала:
- Консоль
- Отладка
- ИсточникСобытий
- Журнал событий (только при запуске в Windows)
- Включает проверку области и проверку зависимостей, если используется среда
Development.
HostApplicationBuilder.Services Это Microsoft.Extensions.DependencyInjection.IServiceCollection экземпляр. Эти службы используются для сборки IServiceProvider , которая используется с внедрением зависимостей для разрешения зарегистрированных служб.
Службы, предоставляемые фреймворком
При вызове любой из служб IHostBuilder.Build() или HostApplicationBuilder.Build() автоматически регистрируются следующие службы:
Дополнительные средства создания хостов на основе сценариев
Если вы создаете веб-приложение или пишете распределенное приложение, может потребоваться использовать другой построитель узлов. Рассмотрим список дополнительных построителей хостов.
- DistributedApplicationBuilder: построитель для создания распределенных приложений. Дополнительные сведения см. в статье Aspire.
- WebApplicationBuilder: построитель веб-приложений и служб. Дополнительные сведения см. в статье об ASP.NET Core.
-
WebHostBuilder: конструктор для
IWebHost. Дополнительные сведения см. на веб-узле ASP.NET Core.
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.");
}
}
Шаблон Worker Service можно изменить, чтобы добавить реализацию 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.StartingAsyncIHostedService.StartAsyncIHostedLifecycleService.StartedAsyncIHostApplicationLifetime.ApplicationStarted
Если приложение остановлено, например нажатием Ctrl+C, возникают следующие события:
IHostApplicationLifetime.ApplicationStoppingIHostedLifecycleService.StoppingAsyncIHostedService.StopAsyncIHostedLifecycleService.StoppedAsyncIHostApplicationLifetime.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 служба предоставляет возможность оценивать среду с помощью этих методов расширения:
- HostEnvironmentEnvExtensions.IsDevelopment
- HostEnvironmentEnvExtensions.IsEnvironment
- HostEnvironmentEnvExtensions.IsProduction
- HostEnvironmentEnvExtensions.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.Configuration свойство. Это свойство позволяет потребителям считывать или вносить изменения в существующую конфигурацию с помощью доступных методов расширения.
Дополнительные сведения см. в статье Конфигурация в .NET.
Завершение работы узла
Существует несколько способов остановки хостированного процесса. Чаще всего размещенный процесс можно остановить следующим образом:
- Если кто-то не вызывает Run или HostingAbstractionsHostExtensions.WaitForShutdown, приложение завершает работу обычным образом после выполнения
Main. - Если приложение аварийно завершит работу.
- Если приложение принудительно завершает работу с помощью SIGKILL (или CTRL+Z).
Хостинговый код не отвечает за обработку этих сценариев. Владелец процесса должен иметь дело с ними так же, как и с любым другим приложением. Есть несколько других способов остановить хостируемый процесс службы:
- Если
ConsoleLifetimeиспользуется (UseConsoleLifetime), он прослушивает следующие сигналы и пытается корректно остановить хост. - Если приложение вызывает метод Environment.Exit.
Встроенная логика размещения обрабатывает эти сценарии, в частности ConsoleLifetime класс.
ConsoleLifetime пытается обработать сигналы завершения работы SIGINT, SIGQUIT и SIGTERM, чтобы обеспечить корректное завершение работы приложения.
До выхода .NET 6 не существовало способа корректной обработки SIGTERM кодом .NET. Чтобы обойти это ограничение, ConsoleLifetime подключался к System.AppDomain.ProcessExit. Когда ProcessExit срабатывал, ConsoleLifetime сигнализировал хосту об остановке и блокировал поток ProcessExit, ожидая, пока хост перестанет работать.
Обработчик выхода процесса позволит выполнить код очистки в приложении, например, IHost.StopAsync и код после HostingAbstractionsHostExtensions.Run в методе Main.
Однако существуют и другие проблемы с этим подходом, поскольку SIGTERM не был единственным способом, которым ProcessExit инициировался. SIGTERM также вызывается, когда код приложения вызывает Environment.Exit. Метод Environment.Exit не является корректным способом завершения работы процесса в модели приложения Microsoft.Extensions.Hosting. Он порождает событие ProcessExit, а затем завершает процесс. Конец метода Main не выполняется. Фоновые и передние потоки завершаются, а finally блоки не выполняются.
Поскольку ConsoleLifetime заблокирован в ожидании завершения работы узла, это поведение привело к взаимоблокировкам, когда 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.
Когда хост получает сигнал завершения работы (например, Ctrl+C или StopAsync), он уведомляет приложение, отправляя сигнал ApplicationStopping. Вам следует подписаться на это событие, если у вас есть длительные операции, которые должны завершиться корректно.
Затем хост вызывает IServer.StopAsync с настраиваемым временем ожидания завершения работы (по умолчанию 30 с). Kestrel (и Http.Sys) закрывают привязки портов и перестают принимать новые подключения. Они также сообщают текущим подключениям о прекращении обработки новых запросов. Для HTTP/2 и HTTP/3 предварительное GOAWAY сообщение отправляется клиенту. Для HTTP/1.1 они останавливают цикл подключения, так как запросы обрабатываются по порядку. IIS ведет себя иначе, отклоняя новые запросы с кодом 503.
Активные запросы могут завершиться до истечения времени ожидания завершения работы. Если все они завершены до истечения времени ожидания, сервер возвращает контроль хосту раньше. Если истекает время ожидания, ожидающие подключения и запросы прерываются принудительно, что может вызвать ошибки в журналах и на стороне клиентов.
Особенности балансировщика нагрузки
Чтобы обеспечить плавное переход клиентов в новое место назначения при работе с подсистемой балансировки нагрузки, выполните следующие действия.
- Откройте новый экземпляр и начните балансировку трафика к нему (возможно, у вас уже есть несколько экземпляров для масштабирования).
- Отключите или удалите старый экземпляр в конфигурации подсистемы балансировки нагрузки, чтобы он перестал получать новый трафик.
- Отправьте сигнал на завершение работы старого экземпляра.
- Подождите, пока он будет стекать или истекает время ожидания.