14

SimpleInjectorIoCライブラリとして使用しています。DbContextWebリクエストに従って登録しましたが、正常に機能します。しかし、バックグラウンドスレッドで実行するタスクが1つあります。DbContextそのため、インスタンスの作成に問題があります。例えば

  1. Service1のインスタンスがありますDbContext
  2. Service2のインスタンスがありますDbContext
  3. Service1Service2バックグラウンドスレッドから実行します。
  4. Service1エンティティをフェッチしてに渡しますService2
  5. Service2そのエンティティを使用しますが、エンティティはから切り離されていますDbContext

実際に問題はここにあります:Service1.DbContextとの違いですService2.DbContext

ASP.NET MVCの別のスレッドでタスクを実行すると、呼び出しごとSimpleInjectorにの新しいインスタンスが作成されるようです。DbContext一部のIoCライブラリ(たとえばStructureMap)には、スレッドごと、Webリクエストごとのライフスタイルが混在していますが、そうでSimpleInjectorはないようです。私は正しいですか?

この問題を解決するためのアイデアはありSimpleInjectorますか?前もって感謝します。

編集:

私のサービスはここにあります:

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

4

1 に答える 1

17

ASP.NET MVCの別のスレッドでタスクを実行すると、SimpleInjectorは呼び出しごとにDbContextの新しいインスタンスを作成するようです。

Simple Injector v1.5以下のライフスタイルの動作は、RegisterPerWebRequestインスタンスがWebリクエストのコンテキスト外でリクエストされた場合(HttpContext.Currentnullの場合)に一時的なインスタンスを返すことです。一時的なインスタンスを返すことは、不適切な使用法を簡単に隠すことができるため、SimpleInjectorの設計上の欠陥でした。Simple Injectorのバージョン1.6は、一時的なインスタンスを誤って返すのではなく、例外をスローして、コンテナーが誤って構成されたことを明確に伝えます。

一部のIoCライブラリ(StructureMapなど)には、スレッドごと、Webリクエストごとのライフスタイルが混在していますが、SimpleInjectorにはないようです。

いくつかの理由により、SimpleInjectorには混合ライフスタイルのサポートが組み込まれていないのは正しいことです。まず第一に、それは多くの人が必要としない非常にエキゾチックな機能です。第二に、あなたは2つか3つのライフスタイルを一緒に混ぜることができるので、それはハイブリッドのほぼ無限の組み合わせになるでしょう。そして最後に、これを自分で登録するのは(かなり)簡単です。

Webリクエストごととスレッドごとのライフスタイルを混在させることはできますが、Webリクエストごととライフタイムスコープごとを混在させると、スコープを明示的に開始および終了するため(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の質問があります。ご覧になると、次のようになります。Simple Injector:MVC3ASP.NETのマルチスレッド

アップデート

アップデートについて。もうすぐです。バックグラウンドスレッドで実行されるコマンドは、ライフタイムスコープ内で実行する必要があるため、明示的に開始する必要があります。ここでの秘訣は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);

この古いStackoverflowの質問では、これについて詳しく説明しています。あなたは間違いなく見てみるべきです。

于 2012-10-30T21:39:57.800 に答える