我有一个Web Api控制器。控制器有一个构造函数,它需要一个给定的接口。接口通过依赖注入(DI)注入。
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
}
注入的接口(IMyManager)实现使用Entity Framework存储库的方法。有一些方法可以不断访问永不改变的表。由于控制器使用频繁,我注意到由于这些连续调用DB而产生的大量内存流量以及实现/处理对象的成本。上下文(ISomeContext)也是通过DI创建的。
我创建了仅实例化一次的静态私有成员,但这种方法工作正常,因为它是一个多线程进程,我需要锁定对象以确保实例只生成一次。通过这种方法,我摆脱了那些数据库和实现/处置成本,并且锁定等待时间的惩罚。
public class MyManager : IMyManager
{
protected ISomeContext _someContext;
private static IList<MyPOCO> _myTableList;
private static readonly object MyTableListLock = new object();
public FileUploadManager(ISomeContext someContext)
{
_someContext = someContext;
//I want to avoid using this lock... A lazy implementation perhaps?
lock (MyTableListLock)
{
if (_myTableList == null)
{
_myTableList = _someContext.MyPOCO.ToList();
}
}
}
}
您是否有任何想法如何在没有锁的情况下实现上一代码的相同结果?我正在考虑一个懒惰的实现,但是由于存储库是非静态的,我有点迷失。
提前致谢,
卡洛斯
答案 0 :(得分:1)
轻量级锁定
通过使用ReaderWriterLockSlim将读锁定与写锁定区分到缓存,可以获得更好的多线程吞吐量。我根据one of my designs设计this article,将ReaderWriterLockSlim
与惰性锁模式结合起来。
private ReaderWriterLockSlim synclock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
public T GetOrAdd(string key, Func<T> loadFunction)
{
LazyLock lazy;
bool success;
synclock.EnterReadLock();
try
{
success = this.cacheProvider.TryGetValue(key, out lazy);
}
finally
{
synclock.ExitReadLock();
}
if (!success)
{
synclock.EnterWriteLock();
try
{
if (!this.cacheProvider.TryGetValue(key, out lazy))
{
lazy = new LazyLock();
this.cacheProvider.Add(key, lazy);
}
}
finally
{
synclock.ExitWriteLock();
}
}
return lazy.Get(loadFunction);
}
private sealed class LazyLock
{
private volatile bool got;
private object value;
public TValue Get<TValue>(Func<TValue> activator)
{
if (!got)
{
if (activator == null)
{
return default(TValue);
}
lock (this)
{
if (!got)
{
value = activator();
got = true;
}
}
}
return (TValue)value;
}
}
处置上下文
由于您没有报告除内存消耗之外的任何性能问题,因此很可能您没有清理实体框架上下文,这样做会释放内存。 WebApi有一个机制。覆盖控制器中的Dispose()
方法。
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_myManager.Dispose();
}
base.Dispose(disposing);
}
}
当然,您需要确保您的经理设置为通过调用Dispose()
来正确释放实体框架上下文。请注意,控制器始终被实例化并处理在单个请求的范围内。
或者,您始终可以确保在using语句(在IMyManager中)中使用您的Entity Framework上下文。
using (var context = new MyEFContext())
{
// Run your db action here
}
有关其他可能的替代方案,请参阅this answer。
答案 1 :(得分:1)
我遵循了Matt关于将责任委托给DI Container(Autofac)的建议。我正在分享用于解决锁定问题的代码片段。
1)我创建了一个处理静态数据的独立管理器,并由其他管理器在内部引用,它看起来像这样:
public sealed class DataManager : IDataManager
{
static DataManager()
{
using (var context = new MyDataContext())
{
_queryableTable1 = context.StaticTable1.AsNoTracking()
.ToList()
.AsQueryable();
}
}
private static readonly IQueryable<StaticTable1> _queryableTable1;
public IQueryable<StaticTable1> QueryableTable1
{
get { return _queryableTable1; }
}
}
界面如下所示:
public interface IDataManager
{
IQueryable<StaticTable1> QueryableTable1 { get; }
}
2)有两个选项,通过构造函数或属性注入类。在我的例子中,我使用了属性注入,其属性为引用主管理器的控制器自动装配。两种方法都有效。代码如下所示:
var builder = new ContainerBuilder();
builder.RegisterType<DataManager>()
.As<IDataManager>()
.SingleInstance();
//The rest of the container registration go here...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
3)主要经理看起来像这样:
public class MainManager : IMainManager
{
//Option A: Property injection with public property
public IDataManager DataManager { get; set; }
//Option B: Constructor injection with private member
private IDataManager _dataManager;
public MainManager (IDataManager dataManager)
{
_dataManager = dataManager;
}
//This method shows how to consume the DataManager.
public void MyMethod()
{
var row = DataManager.QueryableTable1.FirstOrDefault();
//Some more logic....
}
}
4)在属性注入的情况下,可以选择将属性添加到界面,如下所示:
public interface IMyManager
{
//This is optional.
IDataManager DataManager { get; set; }
//This method shows how to consume the DataManager.
public void MyMethod();
}
DataManager是公共的,但是它有一个静态构造函数,它只在第一次实例化静态类型。实例的创建被委托给Autofac,因此可以保证实例只创建一次,并且通过指定&#34; Singleton&#34;来保证线程安全。在我的情况下,控制器为每个请求/每个生命周期范围创建实例,因此对于每个控制器,都有一个将在实例之间共享的DataManager实例。
注意:这不是完整的代码,但我测试了它并且工作正常。