【问题标题】:How to use Ninject in a multi-threaded Windows service to get new instances of a dependency (DbContext) on every tick?如何在多线程 Windows 服务中使用 Ninject 在每个滴答时获取依赖项(DbContext)的新实例?
【发布时间】:2014-02-10 10:27:53
【问题描述】:

我继承了一个 Windows 服务,其中所有依赖项都是在服务启动时创建并注入到瞬态范围内。

我们在使用这个服务时遇到了很多问题,尤其是我们有一个 DbContext,它在服务运行的整个过程中都存在,并且每次都会注入它的不同实例。

我想重构,以便每个工作线程都有自己的 DbContext 注入,它将在每个滴答声的持续时间内存在。

我看过custom scope。对于单线程应用程序来说它看起来不错,但对于多线程应用程序却不是。我也考虑过InThreadScope。虽然这会给每个线程它自己的实例,但就线程而言,它们是单例的,因此它不满足每次滴答的要求。

我目前的想法是使用named scope extension 并注入一个作用域工厂,我可以使用它在每个刻度上创建一个新作用域。

这是要走的路吗?任何建议、提示或替代方案将不胜感激。

更新

由于时间限制,我们最终使用了命名范围,但它不如@BatteryBackupUnit 的解决方案干净。在图的下方有一些依赖项需要 DbContext,我们必须再次注入作用域工厂来获取它。使用@BatteryBackupUnit 的解决方案,我们可以重用ThreadLocal 存储中的相同实例。

【问题讨论】:

    标签: c# multithreading entity-framework ninject


    【解决方案1】:

    关于命名范围:考虑到当您从同一个线程但从在创建范围之前创建的对象(例如工厂)创建 DbContext 时,它将不起作用。要么因为没有作用域而失败,要么因为有不同的作用域而注入另一个 DbContext 实例。 如果你不这样做,那么像命名作用域或调用作用域这样的作用域可以为你工作。

    我们正在执行以下操作:

    当请求 DbContext 时,我们检查 ThreadLocal (http://msdn.microsoft.com/de-de/library/dd642243%28v=vs.110%29.aspx) 是否已经有一个。如果有,我们使用那个。否则,我们创建一个新的并将其分配给ThreadLocal<DbContext>.Value。 完成所有操作后,我们释放 DbContext 并重置 ThreadLocal<DbContext>.Value

    查看这个(简化的,不完美的)代码示例:

    public interface IUnitOfWork
    {
        IUnitOfWorkScope Start();
    }
    
    internal class UnitOfWork : IUnitOfWork
    {
        public static readonly ThreadLocal<IUnitOfWorkScope> LocalUnitOfWork = new ThreadLocal<IUnitOfWorkScope>();
    
        private readonly IResolutionRoot resolutionRoot;
    
        public UnitOfWork(IResolutionRoot resolutionRoot)
        {
            this.resolutionRoot = resolutionRoot;
        }
    
        public IUnitOfWorkScope Start()
        {
            if (LocalUnitOfWork.Value == null)
            {
                LocalUnitOfWork.Value = this.resolutionRoot.Get<IUnitOfWorkScope>();
            }
    
            return LocalUnitOfWork.Value;
        }
    }
    
    public interface IUnitOfWorkScope : IDisposable
    {
        Guid Id { get; }
    }
    
    public class UnitOfWorkScope : IUnitOfWorkScope
    {
        public UnitOfWorkScope()
        {
            this.Id = Guid.NewGuid();
        }
    
        public Guid Id { get; private set; }
    
        public void Dispose()
        {
            UnitOfWork.LocalUnitOfWork.Value = null;
        }
    }
    
    public class UnitOfWorkIntegrationTest : IDisposable
    {
        private readonly IKernel kernel;
    
        public UnitOfWorkIntegrationTest()
        {
            this.kernel = new StandardKernel();
            this.kernel.Bind<IUnitOfWork>().To<UnitOfWork>();
            this.kernel.Bind<IUnitOfWorkScope>().To<UnitOfWorkScope>();
        }
    
        [Fact]
        public void MustCreateNewScopeWhenOldOneWasDisposed()
        {
            Guid scopeId1;
            using (IUnitOfWorkScope scope = this.kernel.Get<IUnitOfWork>().Start())
            {
                scopeId1 = scope.Id;
            }
    
            Guid scopeId2;
            using (IUnitOfWorkScope scope = this.kernel.Get<IUnitOfWork>().Start())
            {
                scopeId2 = scope.Id;
            }
    
            scopeId1.Should().NotBe(scopeId2);
        }
    
        [Fact]
        public void NestedScope_MustReuseSameScope()
        {
            Guid scopeId1;
            Guid scopeId2;
            using (IUnitOfWorkScope scope1 = this.kernel.Get<IUnitOfWork>().Start())
            {
                scopeId1 = scope1.Id;
                using (IUnitOfWorkScope scope2 = this.kernel.Get<IUnitOfWork>().Start())
                {
                    scopeId2 = scope2.Id;
                }
            }
    
            scopeId1.Should().Be(scopeId2);
        }
    
        [Fact]
        public void MultipleThreads_MustCreateNewScopePerThread()
        {
            var unitOfWork = this.kernel.Get<IUnitOfWork>();
            Guid scopeId1;
            Guid scopeId2 = Guid.Empty;
            using (IUnitOfWorkScope scope1 = unitOfWork.Start())
            {
                scopeId1 = scope1.Id;
                Task otherThread = Task.Factory.StartNew(() =>
                    {
                        using (IUnitOfWorkScope scope2 = unitOfWork.Start())
                        {
                            scopeId2 = scope2.Id;
                        }
                    },
                    TaskCreationOptions.LongRunning);
                if (!otherThread.Wait(TimeSpan.FromSeconds(5)))
                {
                    throw new TimeoutException();
                }
            }
    
            scopeId2.Should().NotBeEmpty();
            scopeId1.Should().NotBe(scopeId2);
        }
    
        public void Dispose()
        {
            this.kernel.Dispose();
        }
    }
    

    注意:我正在使用 nuget 包:ninject、xUnit.Net、Fluent Assertions

    另请注意,您可以将 IUnitOfWork.Start 替换为 ToProvider&lt;IUnitOfWorkScope&gt;() 绑定。当然需要在provider中实现相应的逻辑。

    【讨论】:

      【解决方案2】:

      Ninject.Extensions.UnitOfWork 中实现的适当工作单元范围解决了这个问题。

      设置:

      _kernel.Bind<IService>().To<Service>().InUnitOfWorkScope();
      

      用法:

      using(UnitOfWorkScope.Create()){
          // resolves, async/await, manual TPL ops, etc    
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多