Когда создается Singleton сервис в ASP.NET Core: при старте приложения или при первом обращении?
Что проверяют
Знаете ли вы реальное поведение встроенного контейнера, а не общее описание паттерна. Правильный ответ: при первом обращении, и интервьюер ждёт, что вы назовёте исключения. Следом обычно спрашивают про внедрение scoped-сервиса в singleton.
Как отвечать
Регистрация через AddSingleton<IFoo, Foo>() или с фабрикой ничего не создаёт. Контейнер Microsoft.Extensions.DependencyInjection строит экземпляр лениво, когда сервис впервые запрашивают: напрямую через GetRequiredService или как зависимость другого сервиса, например контроллера при первом HTTP-запросе. Дальше этот экземпляр получают все, создание потокобезопасно, и второй не появится даже при параллельных запросах.
Когда singleton появляется раньше первого запроса:
- готовый экземпляр:
AddSingleton<IFoo>(new Foo())создаёте вы сами на этапе регистрации; - hosted-сервисы: реализации
IHostedServiceиBackgroundServiceрезолвятся при старте хоста, а вместе с ними и их singleton-зависимости; - явный прогрев: после
builder.Build()вызватьapp.Services.GetRequiredService<Foo>().
Прогрев нужен, если конструктор тяжёлый: загрузка справочников, подключение к брокеру. Иначе эту задержку получит первый пользовательский запрос.
При остановке приложения контейнер вызывает Dispose или DisposeAsync у singleton-ов, которые создал сам. Экземпляр, переданный готовым объектом, контейнер не освобождает, это ваша ответственность.
Стоит сказать и то, что это не паттерн Singleton с приватным конструктором, а время жизни в контейнере: new Foo() в обход DI создаст второй экземпляр.
Где спотыкаются
Уверенно говорят «при старте приложения». Это самая частая ошибка, и она показывает, что поведение контейнера вы не проверяли.
Внедряют scoped-сервис, чаще всего DbContext, в singleton. Получается captive dependency: один контекст на всё приложение и гонки между запросами. В Development это ловит ValidateScopes, в Production проверка по умолчанию выключена.
Хранят в singleton изменяемое состояние в обычном Dictionary или List. Экземпляр один на все потоки, нужны ConcurrentDictionary, блокировки или неизменяемые структуры.