Конкурентный Запуск и Остановка Hosted-Сервисов в .NET 8
Библиотека Microsoft.Extensions.Hosting, используемая как в шаблоне проекта ASP.NET Core, так и в шаблоне Worker Service, обеспечивает возможность запуска долго работающего консольного приложения. В .NET 8 в ней появилась новая функциональность, влияющая на размещённые (hosted) сервисы.
В ASP.NET Core приложение запускает веб-сервер Kestrel и работает до тех пор, пока процесс не будет остановлен. Шаблон Worker Service идеально подходит для создания легковесных приложений обработки, которые работают постоянно. Например, это могут быть микросервисы, которые запрашивают сообщения из очереди и обрабатывают их.
Размещённые сервисы — это фоновые задачи, запускаемые хостом, которые выполняют работу приложения. Приложения могут определять один или несколько размещённых сервисов, регистрируя реализацию интерфейса
IHostedService в DI-контейнере. Microsoft предоставляет общую реализацию этого интерфейса в абстрактном классе BackgroundService. Разработчики наследуют от этого класса и предоставляют реализацию абстрактного метода ExecuteAsync. Когда платформа размещения запускается, она запускает задачу, определённую в методе ExecuteAsync.До .NET 8 код, который запускает и останавливает размещённые сервисы, делал это последовательно. Каждый
IHostedService запускался последовательно через ожидание вызова StartAsync экземпляра. Т.е. каждая задача StartAsync должна была быть завершена до запуска следующей. Это не оказывало существенного влияния на большинство приложений, но это поведение по умолчанию всё же может вызывать проблемы. Несмотря на то, что внутри StartAsync рекомендуется выполнять небольшую работу, медленный размещённая сервис может задерживать запуск оставшейся части приложения.При остановке приложений сервисы так же останавливались в обратном порядке через ожидание
StopAsync. Это может быть более проблематичным, т.к. настроен тайм-аут (ShutdownTimeout), ограничивающий продолжительность корректного завершения работы. Т.е. тайм-аут должен учитывать время, необходимое для корректного закрытия всех размещённых сервисов. В некоторых ситуациях при сбросе текущих незавершённых работ это может привести к тому, что некоторые сервисы будут использовать большую часть тайм-аута.В .NET 8 добавлены две новые опции, которые позволяют переключаться на параллельный запуск и/или остановку:
var host = Host.CreateDefaultBuilder(args)В примере выше регистрируются два размещённых сервиса, которые будут запускаться и останавливаться одновременно, не задерживая друг друга. Внутри .NET это достигается путём добавления всех сервисов в группу задач и ожидания завершения этой группы через Task.WhenAll. Для конкурентного останова это позволяет каждому размещённому сервису завершить свою работу в течение всего времени тайм-аута.
.ConfigureServices(svc =>
{
svc.Configure<HostOptions>(o =>
{
o.ServicesStartConcurrently = true;
o.ServicesStopConcurrently = true;
});
svc.AddHostedService<WorkerOne>();
svc.AddHostedService<WorkerTwo>();
})
.Build();
Существующее последовательное поведение остается по умолчанию. Как правило, переключение в параллельный режим считается безопасным для большинства приложений. Однако, если ваши размещённые сервисы напрямую зависят друг от друга каким-то образом, одновременный запуск или остановка могут вызвать ошибки в вашем приложении, поскольку вы не сможете полагаться на то, что необходимый сервис будет полностью запущен.
Источник: https://www.stevejgordon.co.uk/concurrent-hosted-service-start-and-stop-in-dotnet-8