Ninject和异步操作

本文关键字:异步操作 Ninject | 更新日期: 2023-09-27 18:21:53

我已经在谷歌上搜索了一些关于如何将Ninject与异步/等待操作一起使用的有用示例。我自己也尝试过,在不需要写很多代码的情况下,找到任何可以让它真正工作的模式,但我还没有成功。

对基本问题最简单的描述是,实体框架DbContext是在每个请求范围的基础上创建的(它是一个ASP.NET MVC应用程序),但如果你试图调用任何*Async方法,它都会失败,并显示它已经在执行了(这很明显)。

因此,我需要的是调用Kernel.Get<MyContext>()使用Ninject创建唯一的DbContext对象,并且由Ninject负责其生命周期。使用BeginBlock()或将作用域更改为InTransientScope()并不是一个真正的选项,因为第一种方法会使代码变得非常沉重,并带有单独的块和处理这些块,而后者则将调用方代码设置为负责处理DbContext

我想做的代码的POC示例:

var context1 = NinjectKernelReference.Get<MyContext>(); //I want this to be a unique reference
var context2 = NinjectKernelReference.Get<MyContext>(); //I want this to be a unique reference
var task1 = context1.Customers.Where(c => c.ZipCode == "4444")
                    .Select(c => new {
                                         c.Name,
                                         c.PhoneNumber
                                     })
                    .Take(50)
                    .ToArrayAsync();
var task2 = context2.Customers.CountAsync(c => c.ZipCode == "4444");
Task.WaitAll(task1, task2);
return new Result { task1.Result, task2.Result };

那么,有什么"简单"的方法可以解决这个问题吗?

Ninject和异步操作

这就是您自己控制对象创建的方法:

public interface IContextFactory {
    MyContext Create();
}
kernel.Bind<IContextFactory>().ToFactory // requires ninjext.extensions.factory

(工厂扩展链路)

用法:

using(var context1 = IContextFactory.Create())
using(var context2 = IContextFactory.Create())
{
    var task1 = context1.Customers.Where(c => c.ZipCode == "4444")
                .Select(c => new {
                                     c.Name,
                                     c.PhoneNumber
                                 })
                .Take(50)
                .ToArrayAsync();
    var task2 = context2.Customers.CountAsync(c => c.ZipCode == "4444");
    Task.WaitAll(task1, task2);
    return new Result { task1.Result, task2.Result };
} // context get's disposed here

注意:这需要上下文而不是绑定.InRequestScope(),否则,两个.Create()调用都将返回相同的对象。如果有时需要.InRequestScope(),有时不需要,可以考虑使用ninject上下文绑定

经过一些挖掘和阅读其他用户对这篇文章的回应,我意识到这不是一个简单的方法。

因此,我决定使用IKernel.BeginBlock在并行线程执行中创建一个"本地"Ninject作用域,我在这里执行逻辑。

它可能没有我想要的那么整洁,但它很管用。

using( var block1 = NinjectKernelReference.BeginBlock())
using( var block2 = NinjectKernelReference.BeginBlock())
{
    var context1 = block1.Get<MyContext>(); 
    var context2 = block2.Get<MyContext>(); 
    var task1 = context1.Customers.Where(c => c.ZipCode == "4444")
                        .Select(c => new {
                                             c.Name,
                                             c.PhoneNumber
                                         })
                        .Take(50)
                        .ToArrayAsync();
    var task2 = context2.Customers.CountAsync(c => c.ZipCode == "4444");
    Task.WaitAll(task1, task2);
    return new Result { task1.Result, task2.Result };
}

它可能没有那么整洁,但它很有效,我看不出有任何负面影响(除了BeginBlock和Dispose的一些小性能问题)。