У меня есть собственная реализация брокера сообщений (по типу MessagePipe), там все тоже реализовано через DI и использование интерфейсов типа IReceiver<T> (для получения), ISender<T> (для отправки).
Я верно понимаю, что до тех пор пока не будет нужды в экземпляре класса, то DI даже не дернется, чтобы создать экземпляр, а следовательно все содержащиеся ISender, IReceiver не будут активны до востребования.
Как обойти это? Просто мне было интересно реализовать приложение, которое работает полностью через события. Есть конечно дешево-сердитый вариант - собственноручно запрашивать экземпляры через GetRequiredService<T>() после сборки контейнера. Мне в голову приходит еще вариант добавить к таким сервисам какой-нибудь интерфейс и уже по интерфейсу запрашивать все классы, но адекватно ли это?
1 ответ 1
Я просмотрел разную информацию и нашел несколько вариантов, некоторое были описаны тут, до некоторых докопался. Я опишу их ниже, может кому-то это пригодится в будущем, кто столкнется с такой же проблемой.
Переформулирую проблему тут, потому что в вопросе я немного неясно выразился. Суть такова: Как активировать все классы, которые содержат брокеры, чтобы на старте программы они могли обмениваться сообщениями? Какой способ наиболее оптимальный?
По стандарту DI-контейнеры используют ленивую инициализацию (lazy loading), т.е. объект создается только в тот момент, когда он потребуется. Так банально эффективнее, но в моем случае классы явно не связаны друг с другом, поэтому момент, когда они потребуются не наступит, а следовательно не будут и созданы подписки. Поэтому нужно как-то автоматически активировать нужные классы.
- Использовать авто-активацию через
Microsoft.Extensions.DependencyInjection.AutoActivation(илиAutofac) (мой выбор)
Ниже будет пример кода для Microsoft, узнал я о таком функционале вот здесь -> issue. Я немного удивлен, что это было реализовано относительно недавно в .NET 8.0.
builder.Services.AddActivatedSingleton<T>();
Для Autofac все еще проще и поддерживается из коробки. Подробнее тут -> документация.
containerBuilder.RegisterType<T>()
.AsSelf()
.AutoActivate();
- Использовать
IHostedService(илиIStartable)
Еще можно сделать каждый сервис, как IHostedService, тогда будет вызван метод StartAsync, в котором должна будет выполняться бесконечно долгая задача по типу:
while(!cts.IsCancellationRequested)
{
...
}
В противном случае Task StartAsync завершится и сервис будет остановлен. Еще я не уверен, что будет, если один IHostedService зависит от другого. Решает ли что-то в этом случае Microsoft DI.
IStartable из Autofac. Здесь делать бесконечно долгую задачу не надо. Надо просто реализовать интерфейс IStartable в нужных сервисах. В случае зависимостей между IStartable Autofac гарантирует, что метод Start зависимости будет вызван раньше, чем у зависящего класса.
- Инициализация ручками
Это самый простой способ. У нас по любому есть общая точка, которая 100% будет существовать. Поэтому нужные классы можно вызвать ручками. Первый способ - это во время запуска приложения. Второй способ - в любом классе программы.
private static IServiceCollection Instantiate(this IServiceCollection services) // Mircosoft.Extensions.DependencyInjection
{
using var provider = services.BuildServiceProvider();
_ = provider.GetRequiredService<T>(); // для каждого класса
return services;
}
private static ContainerBuilder Instantiate(this ContainerBuilder builder) // Autofac
{
using var container = builder.Build();
_ = container.Resolve<T>(); // для каждого класса
return builder;
}
Второй способ - это любая точка внедрения, куда сможет это сделать DI.
public Foo(Boo boo, Goo goo, Loo loo) { ... } // через конструктор
public void Foo(Boo boo, Goo goo, Loo loo) { ... } // через метод
И через иные точки внедрения зависимости: атрибуты, свойства и так далее...
- Через атрибут
Нужно сделать атрибут, условно AutoActivate и при помощи инструментов, таких как Scrutor можно делать это как-то вроде так.
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AutoActivateAttribute : Attribute
{
...
}
public static IServiceCollection AddAutoActivateServices(this IServiceCollection services)
{
services.Scan(scan => scan
.FromAssemblyOf<Program>()
.AddClasses(classes => classes.WithAttribute<AutoActivateAttribute>())
.UsingRegistrationStrategy(RegistrationStrategy.Skip)
.AsSelfWithInterfaces()
);
using var provider = services.BuildServiceProvider()
_ = GetRequiredService<T>();
return services;
}
- Через общий интерфейс
Тоже самое, но только ищем по интерфейсу, затем получаем просто IEnumerable<Наш_интерфейс> и дело готово.
- Через атрибут + интерфейс
Тут вариант в виде комбинации двух подходов выше (4 и 5). Я описывать его не буду, потому что всё, начиная с 4 это буквально самостоятельная реализация велосипеда. И как бы раз я уже нашел хороший способ, то на нем и поеду.
Начните задавать вопросы и получать на них ответы
Найдите ответ на свой вопрос, задав его.
Задать вопрос
IHostedServiceвам поможет.