我们正在使用一个使用池化对象的库(ServiceStack.Redis
的{{1}})。创建对象并将其重用于多个Web请求。但是,每次使用后都应将PooledRedisClientManager
称为,以将对象释放回池中。
默认情况下,如果对象引用具有not been deactivated before,则Ninject仅停用该对象引用。
实际上,池会实例化一个对象并将其标记为活动状态。然后Ninject运行激活管道。在请求结束时(Web请求),Ninject运行调用Dispose
的停用管道(因此池将对象标记为非活动状态)。下一个请求:使用第一个池化实例,池将其标记为活动。但是,在请求结束时,Ninject不会运行其停用管道,因为Dispose
已将此实例标记为已停用(this is in the Pipeline)。
以下是我们在新的MVC项目中添加的一个简单示例,用于演示此问题:
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();
}
并使用其他停用机制(如MVC InTransientScope()
在每次请求后处理对象)。我们失去了Ninject停用过程的好处,需要间接处理对象。
编写一个自定义ActionFilter
,用于检查池是否处于活动状态。这是我到目前为止所写的内容,但我希望其他人看到它的强大程度:
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
包装器,这样我们总能得到一个新的对象实例。然后让客户端对象从the wrapper takes care of disposing it开始变为瞬态。这种方法没有使用Ninject解决池化对象的更广泛概念,只修复了ServiceStack.Redis。
PooledRedisClientManager.DisposablePooledClient<RedisClient>
这些方法中的一种比另一种方法更合适吗?
答案 0 :(得分:1)
到目前为止我还没有使用Redis,所以我不能告诉你如何正确地做到这一点。但我可以给你一些意见:
Disposing不是ActivationPipeline唯一完成的事情。 (例如,它还执行属性/方法注入和激活激活/取消激活操作。)通过使用返回false的自定义激活缓存,即使它已被激活,也会导致再次执行这些其他操作(例如,再次执行属性注入) 。)