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

How to do Parallel operations with Thread Scope in Web app using Ninject

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

我总是运行的具体情况是CPU密集型报告,使用Parallel.ForEach,需要使用Entity Framework DbContext; EF 上下文对于线程必须是唯一的,但在这些特殊报告之外,EF 上下文必须是 InRequestScope。

您如何使用 Ninject 实现这一目标?最好允许将 EF 上下文与 Parallel.ForEach 上的每个任务一起处理,因为使用上下文加载的数据只会保留在上下文中并消耗内存。

请注意,此报告足够大,足以保证 Parallel.ForEach,但也足够小,可以 运行 在 Web 请求上同步进行,并且不会使浏览器超时(<60 秒)。也许我很奇怪,但我 运行 非常需要

该解决方案有几个不同的活动部分,在我看来,这些部分在 Ninject 中的记录并不十分完备。好的一面是,在实施了这样的事情之后,您应该很快就开始适应 Ninject

首先,您需要更改对象的范围,以便它们使用 HttpContext(如果存在),如果不存在,则使用当前线程作为后备。没有这方面的文档,但有一个 DefaultScopeCallback 被添加到设置中。将 属性 设置为您自己的范围回调,它使用 Ninject.Web.Common source 中的相同代码来获取 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());

如果您不喜欢执行上述操作,还有另一种为所有隐式绑定设置默认范围的方法,可以说这种方法更优雅。 Changing default object scope with 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 上下文似乎也被重新使用了。这不是我想要的,所以清除操作很重要。真的,我想要的行为更接近于 InCallScope,但是尝试使用 InCallScope 获取 InRequestScope 作为后备是一堆蠕虫,我将在另一天打开。