【问题标题】:Cross-thread conflicts in StructureMapStructureMap 中的跨线程冲突
【发布时间】:2016-08-04 18:56:34
【问题描述】:

我有一个使用多个数据库分片的 API 应用程序,并使用 StructureMap 进行依赖注入。每个 API 调用中所需的标头之一是 ShardKey,它告诉我此调用正在寻址哪个数据库。为了实现这一点,我有一个名为 ShardingMiddlewareOwinMiddleware 类,其中包含以下代码(为清晰起见,已截断):

var nestedContainer = container.GetNestedContainer();
using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey
{
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db));
    await Next.Invoke(context);
}

这在我的测试环境中运行良好,并通过了一系列集成测试。

但是集成测试实际上是单线程的。当我将它部署到 QA 环境中时,一个真正的应用程序正在通过多个同时调用来攻击我的 API,事情开始呈梨形。费伦斯登:

System.ObjectDisposedException:无法访问已处置的对象。此错误的一个常见原因是释放从依赖注入中解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器负责处理上下文实例。

或其他异常表明 StructureMap 没有可用的MyDbContext 的有效实例。

在我看来,多个线程似乎在某种程度上弄乱了彼此的配置,但对于我来说,我无法理解如何,因为我正在使用嵌套容器来存储每个 API 调用的数据库上下文.

有什么想法可能会出错吗?

更新:我还尝试将我的 Db 上下文抽象为一个接口。没有真正的区别;我仍然收到错误

System.InvalidOperationException:尝试创建“SomeController”类型的控制器时发生错误。确保控制器有一个无参数的公共构造函数。 ---> StructureMap.StructureMapConfigurationException:没有注册默认实例,无法自动确定类型'MyNamespace.IMyDbContext'

更新 2:我解决了问题,但赏金仍然开放。请看下面我的回答。

【问题讨论】:

  • 您的DbContext 可能作为Captive Dependency 保持活跃。确保此依赖项的消费者的生命周期不超过 DbContext 的生命周期,或者 - 甚至更好 - 防止将 DbContext 直接注入消费者。 DbContext 是运行时数据,运行时数据should not be injected into components。而是将 DbContext 隐藏在抽象后面。

标签: c# asp.net multithreading structuremap


【解决方案1】:

嗯...我解决了问题,但我不明白为什么这会有所作为。

这归结为与我最初发布的内容有一些细微的差异,我忽略了这些差异,因为我认为这些细节无关紧要,并且会分散问题的注意力。事实上,我的容器并没有在本地定义;相反,它是我的中间件的受保护属性(它是为了集成测试目的而继承的):

protected IContainer Container { get; private set; }

然后在Invoke()方法内部有一个初始化调用:

Container = context.GetNestedContainer(); // gets the nested container created by a previous middleware class, using the context.Environment dictionary

在整个方法中使用日志记录语句,我得到了以下代码(如问题中所述,添加了日志记录):

_logger.Debug($"Line 1 Context={context.GetHashCode}, Container={Container.GetHashCode()}");
var db = MyDbContext.ForShard(shardKey.Value);  // no need for "using", since DI will automatically dispose
_logger.Debug($"Line 2 Context={context.GetHashCode}, Container={Container.GetHashCode()}");
Container.Configure(cfg => cfg.For<MyDbContext>().Use(db));
await Next.Invoke(context);

令人惊讶的是,日志中的内容如下:

第 1 行上下文=56852305,容器=48376271

第 1 行上下文=88275661,容器=85736099

第 2 行上下文=56852305,容器=85736099

第 2 行上下文=88275661,容器=85736099

太棒了!我的中间件的Container 属性被神奇地替换了!尽管事实上它是用private set 定义的,但无论如何,为了安全起见,我检查了MyDbContext.ForShard() 的代码,并没有发现任何可能弄乱Container 的引用。

那么解决方案是什么?我在初始化之后声明了一个本地 container 变量,并改用它。

现在可以了,但我不明白为什么或如何会有所作为。

赏金给能解释这一点的人。

【讨论】:

  • 我认为,原因与“Why Nested Containers over HttpContext or ThreadLocal Scoping?”中的描述相同。这里structuremap.github.io/the-container/nested-containers
  • @ATechieThought 你能解释一下吗?我一直使用 OwinContext 来获取容器,而不是 HttpContext 或 ThreadLocal 范围。
  • 我的错。我错过了。但我的想法是通过查看上下文值以某种方式涉及 httpcontext。现在,httpcontext 已经不在了,是否有可能涉及 MyDbContext 并且应该将其设置为单例?抱歉,如果,我对问题的推理没有任何帮助。
  • @ATechieThought MyDbContext 绝对应该设置为单例。这将使所有线程使用相同的数据库连接......除非我误解了你?
【解决方案2】:

你应该重写这个:

using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey
{
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db));
    await Next.Invoke(context);
}

因为using 在使用结束时处理您的 dbcontext。

你应该注册工厂:

var dbFactory = ()=>MyDbContext.ForShard(shardKey);
nestedContainer.Configure(cfg => cfg.For<Func<MyDbContext>>().Use(dbFactory));
await Next.Invoke(context);

并注入此 Func 而不是 dbcontext 实例。

【讨论】:

  • 如果你注入一个 Func 而不是一个实例,它会在每个注入 MyDbContext 的类中打开一个新的 Db 连接吗?
  • 视情况而定。默认情况下 - 是的。有问题吗?
  • 不确定是否有问题。我目前的想法是单个数据库连接应该足以完成一个工作单元。我不确定如果有更多连接是否会导致冲突 - 或者如果打开太多连接可能会给数据库带来过度的压力......但这让我担心。基本上,如果我想要每个 UOW 一个连接,我将传递实例,如果我想要每个存储库一个连接,我将使用 Func。谢谢!
  • 如果你愿意,你可以对整个分片使用单个数据库上下文。为此,您应该将其注册为嵌套容器中的单例,并在处理嵌套容器时进行处理。并且根本不使用 dbcontext 。有一本关于依赖注入的好书:Dependency Injection in .Net manning.com/books/dependency-injection-in-dot-net
  • 整个分片肯定没有一个数据库连接。会有多个用户;如果他们中的两个同时查询同一个分片,事情会变得很糟糕。
【解决方案3】:

从日志中我看到的是,第二个请求/线程会覆盖容器并尊重第一个请求/线程的数据库上下文,因此两者都使用相同的连接:

Line 2 Context=56852305, Container=85736099 

应该是

Line 2 Context=56852305, Container=48376271

还是我弄错了,所以我不认为你能解决它。 System.ObjectDisposedException 错误来自 using 用于创建数据库上下文实例的子句,因此Next 代表和 context 被处置。我也没看懂台词

Container = context.GetNestedContainer(); 

也许你已经想到了

Container = container.GetNestedContainer(); 

?我对 StructureMap 不熟悉,但我认为代码应该是这样的

var nestedContainer = Container.GetNestedContainer(c =>
                    {
                        var db = MyDbContext.ForShard(shardKey);
                        c.For<MyDbContext>().Use(db);
                    });

await Next.Invoke(context);

假设容器在dispose时关闭并dispose了db连接。

【讨论】:

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