Смешанный образ жизни для каждого потока и для веб-запроса с простым инжектором - программирование
Подтвердить что ты не робот

Смешанный образ жизни для каждого потока и для веб-запроса с простым инжектором

Я использую SimpleInjector в качестве моей библиотеки IoC. Я регистрирую DbContext в соответствии с веб-запросом, и он отлично работает. Но есть одна задача, которую я запускаю в фоновом потоке. Таким образом, у меня возникла проблема с созданием экземпляров DbContext. например.

  • Service1 имеет экземпляр DbContext
  • Service2 имеет экземпляр DbContext
  • Service1 и Service2 выполняется из фонового потока.
  • Service1 извлекает объект и передает его Service2
  • Service2 использует этот объект, но объект отделяется от DbContext

На самом деле проблема здесь: Service1.DbContext является отличием от Service2.DbContext.

Кажется, когда я запускаю задачу в отдельном потоке в ASP.NET MVC, SimpleInjector создает новый экземпляр DbContext для каждого вызова. Хотя некоторые библиотеки IoC (например, StructureMap) имеют смешанный образ жизни для per-thread-per-webrequest, кажется, что SimpleInjector не имеет одного. Я прав?

Есть ли у вас идея решить эту проблему в SimpleInjector? Спасибо заранее.

EDIT:

Мои услуги здесь:

class Service1 : IService1 {
    public Service1(MyDbContext context) { }
}

class Service2 : IService2 {
    public Service2(MyDbContext context, IService1 service1) { }
}

class SyncServiceUsage {
    public SyncServiceUsage(Service2 service2) {
        // use Service2 (and Service1 and DbContext) from HttpContext.Current
    }
}

class AsyncServiceUsage {
    public AsyncServiceUsage(Service2 service2) {
        // use Service2 (and Service1 and DbContext) from background thread
    }
}

public class AsyncCommandHandlerDecorator<TCommand> 
    : ICommandHandler<TCommand> where TCommand : ICommand {

    private readonly Func<ICommandHandler<TCommand>> _factory;

    public AsyncCommandHandlerDecorator(Func<ICommandHandler<TCommand>> factory) {
        _factory = factory;
    }

    public void Handle(TCommand command) {
        ThreadPool.QueueUserWorkItem(_ => {
            // Create new handler in this thread.
            var handler = _factory();
            handler.Handle(command);
        });
    }
}

void InitializeSimpleInjector() {
    register AsyncCommandHandlerDecorator for services (commands actually) that starts with "Async"
}

Я пользователь Service2 иногда и AsyncService2 другие времена.

4b9b3361

Ответ 1

Кажется, когда я запускаю задачу в отдельном потоке в ASP.NET MVC, SimpleInjector создает новый экземпляр DbContext для каждого вызова.

Поведение стиля RegisterPerWebRequest Simple Injector v1.5 и ниже - это возвращение временного экземпляра, когда экземпляры запрашиваются вне контекста веб-запроса (где HttpContext.Current равно null). Возвращение временного экземпляра было ошибкой дизайна в Simple Injector, так как это облегчает скрытие неправильного использования. Версия 1.6 простого инжектора выведет исключение вместо неправильного возврата временного экземпляра, чтобы четко сообщить, что вы неправильно сконфигурировали контейнер.

Хотя некоторые библиотеки IoC (например, StructureMap) имеют смешанные образ жизни для каждого потока на веб-запрос, кажется, простой инжектор не имеет ни одного

Правильно, что Simple Injector не имеет встроенной поддержки смешанного образа жизни из-за нескольких причин. Прежде всего, это довольно экзотическая особенность, которой мало кто нуждается. Во-вторых, вы можете смешивать любые два или три образа жизни вместе, так что это будет почти бесконечное сочетание гибридов. И последнее, это (довольно) легко зарегистрировать это самостоятельно.

Хотя вы можете смешать за веб-запрос с помощью Per Thread образ жизни, вероятно, было бы лучше, если вы смешиваете Per Web Request с Per Lifetime Scope, так как с помощью Lifetime Scope вы явно запускаете и завершаете область (и может удалять DbContext, когда область действия заканчивается).

Из Simple Injector 2 и дальше вы можете легко смешивать любое количество образы жизни, используя Lifestyle.CreateHybrid. Вот пример:

var hybridLifestyle = Lifestyle.CreateHybrid(
    () => HttpContext.Current != null,
    new WebRequestLifestyle(),
    new LifetimeScopeLifestyle());

// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<DbContext, MyDbContext>(hybridLifestyle);

Есть еще один вопрос о Stackoverflow, который затрагивает эту тему немного глубже, вы можете взглянуть: Простой инжектор: многопоточность в MVC3 ASP.NET

UPDATE

О вашем обновлении. Вы почти там. Команды, выполняемые в фоновом потоке, должны выполняться в пределах Lifetime Scope, поэтому вам нужно будет запустить его явно. Трюк здесь заключается в вызове BeginLifetimeScope в новом потоке, но до того, как будет создан фактический обработчик команд (и его зависимости). Другими словами, лучший способ сделать это - внутри декоратора.

Самое простое решение - обновить ваш AsyncCommandHandlerDecorator, чтобы добавить область:

public class AsyncCommandHandlerDecorator<TCommand> 
    : ICommandHandler<TCommand> where TCommand : ICommand 
{
    private readonly Container _container;
    private readonly Func<ICommandHandler<TCommand>> _factory;

    public AsyncCommandHandlerDecorator(Container container,
        Func<ICommandHandler<TCommand>> factory) 
    {
        _container = container;
        _factory = factory;
    }

    public void Handle(TCommand command) 
    {
        ThreadPool.QueueUserWorkItem(_ => 
        {
            using (_container.BeginLifetimeScope())
            {
                // Create new handler in this thread
                // and inside the lifetime scope.
                var handler = _factory();
                handler.Handle(command);
            }
        });
    }
}

Пуристы, выступающие за принципы SOLID, будут кричать, что этот класс нарушает Принцип единой ответственности, так как этот декоратор выполняет команды в новом потоке и запускает новую сферу жизни. Я бы не стал беспокоиться об этом, так как я думаю, что существует тесная взаимосвязь между запуском фонового потока и началом жизненного цикла (вы не использовали бы его без другого в любом случае). Но все же вы можете легко оставить AsyncCommandHandlerDecorator нетронутым и создать новый LifetimeScopedCommandHandlerDecorator следующим образом:

public class LifetimeScopedCommandHandlerDecorator<TCommand> 
    : ICommandHandler<TCommand> where TCommand : ICommand 
{
    private readonly Container _container;
    private readonly Func<ICommandHandler<TCommand>> _factory;

    public LifetimeScopedCommandHandlerDecorator(Container container,
        Func<ICommandHandler<TCommand>> factory)
    {
        _container = container;
        _factory = factory;
    }

    public void Handle(TCommand command)
    {
        using (_container.BeginLifetimeScope())
        {
            // The handler must be created inside the lifetime scope.
            var handler = _factory();
            handler.Handle(command);
        }
    }
}

Порядок, в котором эти декораторы зарегистрированы, конечно, необходим, так как AsyncCommandHandlerDecorator должен обернуть LifetimeScopedCommandHandlerDecorator. Это означает, что регистрация LifetimeScopedCommandHandlerDecorator должна быть первой:

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(LifetimeScopedCommandHandlerDecorator<>),
    backgroundCommandCondition);

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(AsyncCommandHandlerDecorator<>),
    backgroundCommandCondition);

fooobar.com/info/375214/... более подробно об этом говорит. Вы должны обязательно взглянуть.