我已经思考了几个小时,发现这个任务有些具有挑战性。我正在尝试从基类初始化一个给定对象的静态反射数据缓存,以便它可以从多个线程进行访问。但是我很难想出正确的模式来初始化缓存。
我的第一个想法是只需将静态缓存初始化为null,在构造函数中检查它是否为null,如果不是,则构建并设置它。例如:
这种方法存在一个缺陷,如果在第一个对象还在填充缓存时构造另一个对象,我将会构造两个缓存,第二个缓存会(大概率)覆盖第一个缓存。虽然这可能没问题,因为它们两个都是完整的缓存,但这似乎很糟糕,我希望我们能做得更好。
因此,我的第二个想法是在静态构造函数中初始化缓存,在创建任何实例之前,每个应用程序域只调用一次静态构造函数。StackOverflow上有几个类似问题的回答指向了这个方向。这看起来很不错,直到我意识到派生类型的反射数据对静态构造函数不可用。
我总是可以在构造函数中同步访问,以确保只有一个线程正在创建/填充缓存,而在此期间的任何其他访问都应该被阻塞,但是这样一来,我就只是为了保护一个只发生一次的操作而在每次构造时锁定。我不喜欢这样做的性能影响。
我现在的解决方案是使用Interlocked.Exchange和ManualResetEventSlim设置标志。它看起来像这样:
我的第一个想法是只需将静态缓存初始化为null,在构造函数中检查它是否为null,如果不是,则构建并设置它。例如:
class TestBase
{
private static ConcurrentDictionary<string, PropertyInfo> Cache;
protected TestBase()
{
if(Cache == null)
{
ConcurrentDictionary<string, PropertyInfo> cache =
new ConcurrentDictionary<string, PropertyInfo>();
// Populate...
Cache = cache;
}
}
}
这种方法存在一个缺陷,如果在第一个对象还在填充缓存时构造另一个对象,我将会构造两个缓存,第二个缓存会(大概率)覆盖第一个缓存。虽然这可能没问题,因为它们两个都是完整的缓存,但这似乎很糟糕,我希望我们能做得更好。
因此,我的第二个想法是在静态构造函数中初始化缓存,在创建任何实例之前,每个应用程序域只调用一次静态构造函数。StackOverflow上有几个类似问题的回答指向了这个方向。这看起来很不错,直到我意识到派生类型的反射数据对静态构造函数不可用。
我总是可以在构造函数中同步访问,以确保只有一个线程正在创建/填充缓存,而在此期间的任何其他访问都应该被阻塞,但是这样一来,我就只是为了保护一个只发生一次的操作而在每次构造时锁定。我不喜欢这样做的性能影响。
我现在的解决方案是使用Interlocked.Exchange和ManualResetEventSlim设置标志。它看起来像这样:
class TestBase
{
private static ConcurrentDictionary<string, PropertyInfo> Cache;
private static volatile int BuildingCache = 0;
private static ManualResetEventSlim CacheBuilt =
new ManualResetEventSlim();
protected TestBase()
{
if(Interlocked.Exchange(ref BuildingCache, 1) == 0)
{
Cache = new ConcurrentDictionary<string, PropertyInfo>();
// Populate...
CacheBuilt.Set();
}
CacheBuilt.Wait();
}
}
我怀疑已经有一种被接受或至少已知的方法来做这种事情-这是吗?如果不是,是否有更好的方法来同步缓存初始化?请注意,问题不是如何使缓存访问线程安全,可以通过使用ConcurrentDictionary(或类似)来假定。
this.GetType()
。也许我表达得不够清楚 - 我不知道派生类型(而且不止一个)。也就是说,在基类的静态构造函数中无法执行Type type = typeof(SomeDerivedType);
以构建缓存。 - daveaglickprivate static ConcurrentDictionary<string, PropertyInfo> Cache = new ConcurrentDictionary<string, PropertyInfo>();
),并检查 Count 来确定是否需要构建缓存。 - JamieSee