如何使用Ninject在Web应用程序中使用Thread Scope进行并行操作



每当使用新的DI框架时,我都会反复遇到同样的问题。。。如何运行从HttpRequest启动的大规模并行操作,其中每个线程都需要自己唯一的依赖项副本?就我而言,我使用的是Ninject。

我经常遇到的具体情况是使用Parallel的CPU密集型报告。ForEach,需要使用实体框架DbContext;EF上下文必须是线程唯一的,但在这些特殊报告EF上下文之外,它必须是InRequestScope。

你是如何通过Ninject实现这一点的?优选地,允许将EF上下文与并行上的每个任务一起处理。ForEach,因为用上下文加载的数据只会停留在上下文中并消耗内存。

请注意,这份报告足够大,足以保证平行。ForEach,但足够小,可以在web请求上同步运行,而不会使浏览器超时(<60秒)。也许我很奇怪,但我经常遇到这种需求。

该解决方案有几个不同的移动部件,IMO并不是Ninject中记录得很好的部件。好处是,在实现了这样的东西之后,你应该开始对Ninject感到舒服了

首先,您需要更改对象的作用域,以便它们在HttpContext存在的情况下使用它,如果不存在,则使用当前线程作为回退。没有相关文档,但有一个DefaultScopeCallback不久前添加到设置中。将该属性设置为您自己的作用域回调,该回调在Ninject中使用相同的代码。网状物公共源获取HttpContext,但随后使用"??Thread.CurrentThread"作为回退。在安装NuGet包时应该自动创建的CreateKernel代码中执行此操作。

(我已经替换了StandardScopeCallbacks.Thread(ctx),我以前有Thread。CurrentThread,因为前者可能会在某个时候发生变化。目前,这两者的作用完全相同。)

private static IKernel CreateKernel()
{
var settings = new NinjectSettings{ DefaultScopeCallback = DefaultScopeCallback };
var kernel = new StandardKernel(settings);
// The rest of the default implementation of CreateKernel left out for brevity
}
private static Object DefaultScopeCallback(Ninject.Activation.IContext ctx)
{
var scope = ctx.Kernel.Components.GetAll<INinjectHttpApplicationPlugin>()
.Select(c => c.GetRequestScope(ctx)).FirstOrDefault(s => s != null);
return scope ?? Ninject.Infrastructure.StandardScopeCallbacks.Thread(ctx);
}

另外,不要忘记内核需要作为一个静态对象,以便以后访问。你不想每次都需要一个新的内核;我通过"MyConfig.ObjectFactory"访问了我的。虽然这是服务定位器反模式的代码味道,但我们在这里会尽可能避免反模式。

其次,根据提交描述,DefaultScopeCallback只影响没有显式作用域的显式绑定。所以,如果像我一样,你依赖于一堆没有添加的隐式绑定,现在你需要配置它们:

kernel.Bind(i => i.From(Assembly.GetExecutingAssembly(), Assembly.GetAssembly(typeof(Bll.MyConfig)))
.SelectAllClasses()
.BindToSelf());

如果您不喜欢这样做,那么还有另一种方法可以为所有隐式绑定设置默认范围,这可以说更优雅。使用Ninject 2.2 更改默认对象范围

第三,如果你想在每个并行操作结束时从作用域中清除所有缓存的对象,这样内存使用率就不会因为EF缓存或其他原因而飙升,下面是如何清除作用域为当前线程的Ninject缓存:

Parallel.ForEach(myList, i =>
{
var threadDb = MyConfig.ObjectFactory.Get<MyContext>();
CreateModelsForItem(i, threadDb);
MyConfig.ObjectFactory.Components.Get<Ninject.Activation.Caching.ICache>().Clear(Thread.CurrentThread);
});

请注意,我在最后没有Clear行的情况下进行了一些测试,即使HttpRequest完成了,并且我又多次生成报告,EF上下文似乎也被重新使用了。这不是我想要的,所以Clear操作很重要。实际上,我想要的行为更接近InCallScope,但试图用InCallScope作为后备来获取InRequestScope是我改天会打开的一堆蠕虫。

最新更新