Создайте область, используя IServiceProvider из единственного экземпляра - PullRequest
0 голосов
/ 28 апреля 2020

Допустим, у меня есть свой собственный класс QueueListener<TService, TPayload>, унаследованный от BackgroundService. Он открывает TCP-соединение и прослушивает входящие сообщения. В каждом сообщении я хотел бы инициализировать службу типа TService и передать ей десериализованный экземпляр из JSON экземпляра TPayload. TService будет зарегистрирован как Transient, так что это означает, что он должен быть легковесным и без сохранения состояния, как и обработчик полезной нагрузки (в моей текущей задаче). Для этого я собираюсь внедрить IServiceProvider в конструктор моего QueueListener и создать область действия для каждого получаемого сообщения. Это звучит как план или я переигрываю? Я хочу избежать того, что TService также является синглтоном.

Документация говорит :

Опасно разрешать службу с определенными областями из одноэлементного. Это может привести к неправильному состоянию службы при обработке последующих запросов.

Но я не совсем уверен, что это значит. В BackgroundService невозможно внедрить службу с заданной областью, поскольку у нее есть время жизни Singleton. Они предупреждают меня, чтобы я прекратил делать то же самое, что и я?

UPD # 1

Я объясняю, почему я предполагаю создавать область действия для каждого сообщения. Идея заключается в том, чтобы предотвратить блокировку слушателя при обработке сообщений и предоставить другим разработчикам возможность создавать свои собственные обработчики и выполнять некоторые действия с полученным сообщением. Другие разработчики могут создавать соединения с базой данных, например, во время обработки, и я хочу, чтобы они были закрыты и освобождены после завершения обработки.

Ответы [ 3 ]

1 голос
/ 28 апреля 2020

Во-первых, временные службы не относятся к сфере услуг . Временные службы обычно принадлежат вашему коду и создаются каждый раз, когда они разрешаются из контейнера. Контейнер не кэширует временные службы.

TService будет зарегистрирован как Transient ... Для этого я собираюсь внедрить IServiceProvider в конструктор моего QueueListener и создать область действия каждого полученного сообщения.

Вам не нужна область для разрешения переходных служб. Даже если вы создаете область, область все еще не управляет / владеет временными службами. Это, например, завершение срока действия области действия не завершает время жизни временных служб.

Вы можете просто использовать IServiceProvider, введенный в QueueListener, для разрешения TService. И каждый разрешенный TService должен быть уже таким, каким вы хотите

, легкий и не имеющий состояния обработчик полезной нагрузки

Относительно

Документация гласит:

То, что сказано в документе, может быть неактуально сейчас, так как вы не используете сервисы с областью действия Но в случае, если вы хотите узнать причину:

Опасно разрешать службу с заданной областью из одноэлементного.

Синглтон - это особый вид области действия. Синглтон-сервисы создаются и кэшируются в пределах «root» контекста контейнера, который по сути является самим контейнером.

Если вы разрешаете сервис с областью действия из синглтона, время жизни / область действия, в которой разрешается экземпляр сервиса, и кешируется, скорее всего, область "root". Это приводит к проблеме, когда экземпляр службы с областью действия кэшируется внутри контейнера и используется несколькими клиентскими запросами.

Это опасно , поскольку службы с областью действия должны быть

Службы с ограниченным сроком службы (AddScoped) создаются один раз для каждого запроса клиента (соединения).

1 голос
/ 28 апреля 2020

Зарегистрируйте TService как область и создайте новую область для каждого сообщения. Затем разрешите TService из созданной области. Просто прочитайте Использование выделенной службы в фоновом режиме

Вы можете написать это так:

services.AddHostedService<MyBackgroundService>();
services.AddScoped<IScopedServicePerMessage, ScopedServicePerMessage>();

...

public class MyBackgroundService : BackgroundService
{
    private readonly IServiceProvider _sp;
    public MyBackgroundService(IServiceProvider sp)
    {
        _sp = sp;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        DoWork(stoppingToken);
        return Task.CompletedTask;
    }

    private void DoWork(CancellationToken stoppingToken)
    {
        while(true)
        {
            var msg = GetNextMessage();
            using (var scope = _sp.CreateScope())
            {
                var servicePerMessage = scope.ServiceProvider.GetRequiredService<IScopedServicePerMessage>();
                servicePerMessage.Handle(msg);
            }
        }
    }
    ...
}

По этому поводу:

Разрешить услугу с одной областью опасно. Это может привести к неправильному состоянию службы при обработке последующих запросов.

Речь идет о том случае, когда вы вводите сервис с заданной областью (например, core dbcontext) непосредственно в синглтон. Это не ваше дело.

1 голос
/ 28 апреля 2020

Документация относится к внедрению сервиса с определенными областями в одноэлементный сервис. Поскольку инжекция происходит при построении одноэлементного объекта, в это время будет предоставлена ​​служба с ограниченным объемом. Это эффективно увеличит срок службы ограниченного обслуживания до одноэлементного. Это опасно, поскольку срок службы службы часто выбирается в явном виде, чтобы обеспечить быстрое повторное удаление объекта.

Наиболее распространенным примером может быть контекст базы данных, которому принадлежит соединение с базой данных; Вы хотите как можно скорее освободить это соединение с базой данных, чтобы освободить ресурсы. Но если вы внедрили контекст в одноэлементную службу, он никогда не будет утилизирован.

Это, однако, не означает, что в одноэлементной службе невозможно использовать службы с определенной областью. Это достигается тем, что одноэлементный сервис создает область обслуживания, из которой он затем может извлекать одноэлементные сервисы. Однако важно, чтобы эта сфера услуг была недолгой. Так что возьмите пример из ASP. NET Само ядро, где для каждого запроса создается область обслуживания, и выполняйте что-то подобное. Например, в вашем случае вы можете сделать это для каждого входящего сообщения, если это имеет смысл для вашего приложения.

Чтобы создать область обслуживания, вы должны ввести IServiceScopeFactory; Затем вы можете создать с ним область видимости следующим образом:

public async Task Process(TPayload payload)
{
    using (var scope = _serviceScopeFactory.CreateScope())
    {
        var service = scope.GetService<TService>();

        await service.Process(payload);
    }
}

Этот шаблон строго необходим только в том случае, если вам нужно использовать сервисы с областью действия. Вы можете разрешить все другие службы напрямую, не создавая область действия. Если вы можете повторно использовать один и тот же экземпляр службы для обработки всех полезных нагрузок, вы также можете добавить службу как одиночную (то же самое, что зарегистрировать ее как переходную, но разрешить ее только один раз). Если вам нужен экземпляр fre sh для каждой полезной нагрузки, подумайте о создании области действия, даже если в этом нет особой необходимости.

...