StructureMap中的跨线程冲突

本文关键字:线程 冲突 StructureMap | 更新日期: 2023-09-27 18:01:04

我有一个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时,事情开始变成pear-shape。Ferinstance:

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

或者指示StructureMap没有可用的有效MyDbContext实例的其他异常。

对我来说,多个线程似乎在某种程度上扰乱了彼此的配置,但在我的生活中,我不明白是怎么回事,因为我正在使用嵌套容器来存储每个API调用的数据库上下文。

你知道这里可能出了什么问题吗?

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

System.InvalidOperationException:尝试创建"SomeController"类型的控制器时出错。请确保控制器具有无参数的公共构造函数。--->StructureMap.StructureMapConfigurationException:没有注册默认实例,无法为类型"MyNamespace.IMyDbContext"自动确定

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

StructureMap中的跨线程冲突

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

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

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变量,并使用了它。

它现在起作用了,但我不明白为什么或如何会有不同。

赏金归能解释这一点的人所有。

您应该重写这个:

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实例。

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

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);

假设容器关闭并在数据库连接被释放时将其释放。