如何将Ninject配置为始终停用池引用
本文关键字:引用 Ninject 配置 | 更新日期: 2023-09-27 18:17:57
我们正在使用一个使用池对象的库(ServiceStack.Redis
的PooledRedisClientManager
)。对象是为多个web请求创建和重用的。但是,每次使用后,应该调用Dispose
来将对象释放回池中。
默认情况下,Ninject只在之前未被停用时才停用对象引用。
池实例化一个对象并将其标记为活动。然后Ninject运行激活管道。在请求(web请求)结束时,Ninject运行调用Dispose
的停用管道(因此池将对象标记为非活动)。下一个请求:使用第一个池实例,池将其标记为活动。然而,在请求结束时,Ninject没有运行它的去激活管道,因为ActivationCache
已经将这个实例标记为去激活(这在管道中)。
public interface IFooFactory
{
IFooClient GetClient();
void DisposeClient(FooClient client);
}
public class PooledFooClientFactory : IFooFactory
{
private readonly List<FooClient> pool = new List<FooClient>();
public IFooClient GetClient()
{
lock (pool)
{
var client = pool.SingleOrDefault(c => !c.Active);
if (client == null)
{
client = new FooClient(pool.Count + 1);
client.Factory = this;
pool.Add(client);
}
client.Active = true;
return client;
}
}
public void DisposeClient(FooClient client)
{
client.Active = false;
}
}
public interface IFooClient
{
void Use();
}
public class FooClient : IFooClient, IDisposable
{
internal IFooFactory Factory { get; set; }
internal bool Active { get; set; }
internal int Id { get; private set; }
public FooClient(int id)
{
this.Id = id;
}
public void Dispose()
{
if (Factory != null)
{
Factory.DisposeClient(this);
}
}
public void Use()
{
Console.WriteLine("Using...");
}
}
public class HomeController : Controller
{
private IFooClient foo;
public HomeController(IFooClient foo)
{
this.foo = foo;
}
public ActionResult Index()
{
foo.Use();
return View();
}
public ActionResult About()
{
return View();
}
}
// In the Ninject configuration (NinjectWebCommon.cs)
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IFooFactory>()
.To<PooledFooClientFactory>()
.InSingletonScope();
kernel.Bind<IFooClient>()
.ToMethod(ctx => ctx.Kernel.Get<IFooFactory>().GetClient())
.InRequestScope();
}
到目前为止,我们提出的解决方案是:
将这些对象标记为
InTransientScope()
并使用其他停用机制(如MVCActionFilter
在每次请求后处置对象)。我们将失去Ninject的停用过程的好处,并且需要一种间接的方法来处理对象。编写一个自定义
IActivationCache
,检查池中对象是否处于活动状态。以下是我到目前为止所写的内容,但我希望其他人也能看到它的健壮性:public class PooledFooClientActivationCache : DisposableObject, IActivationCache, INinjectComponent, IDisposable, IPruneable { private readonly ActivationCache realCache; public PooledFooClientActivationCache(ICachePruner cachePruner) { realCache = new ActivationCache(cachePruner); } public void AddActivatedInstance(object instance) { realCache.AddActivatedInstance(instance); } public void AddDeactivatedInstance(object instance) { realCache.AddDeactivatedInstance(instance); } public void Clear() { realCache.Clear(); } public bool IsActivated(object instance) { lock (realCache) { var fooClient = instance as FooClient; if (fooClient != null) return fooClient.Active; return realCache.IsActivated(instance); } } public bool IsDeactivated(object instance) { lock (realCache) { var fooClient = instance as FooClient; if (fooClient != null) return !fooClient.Active; return realCache.IsDeactivated(instance); } } public Ninject.INinjectSettings Settings { get { return realCache.Settings; } set { realCache.Settings = value; } } public void Prune() { realCache.Prune(); } } // Wire it up: kernel.Components.RemoveAll<IActivationCache>(); kernel.Components.Add<IActivationCache, PooledFooClientActivationCache>();
特别针对
ServiceStack.Redis
:使用PooledRedisClientManager.DisposablePooledClient<RedisClient>
包装器,所以我们总是得到一个新的对象实例。然后让客户端对象变成暂态的,因为包装器会处理它。这种方法不解决更广泛的概念池对象与Ninject,只修复它为ServiceStack.Redis。var clientManager = new PooledRedisClientManager(); kernel.Bind<PooledRedisClientManager.DisposablePooledClient<RedisClient>>() .ToMethod(ctx => clientManager.GetDisposableClient<RedisClient>()) .InRequestScope(); kernel.Bind<IRedisClient>() .ToMethod(ctx => ctx.Kernel.Get<PooledRedisClientManager.DisposablePooledClient<RedisClient>>().Client) .InTransientScope();
其中一种方法比另一种更合适吗?
我没有使用Redis到目前为止,所以我不能告诉你如何正确地做到这一点。但是我可以给你一些一般性的输入:
dispose并不是ActivationPipeline所做的唯一事情。(例如,它也做属性/方法注入和执行激活/停用动作。)使用自定义激活缓存返回false,即使它之前已经被激活,将导致这些其他动作再次执行(例如导致属性注入再次完成)