我已经思考了几个小时,我对它的挑战性感到有点惊讶。我正在尝试从将要从多个线程访问的基类中为给定对象初始化反射数据的静态缓存。我很难想出初始化缓存的正确模式。
我的第一个想法是,我只需将静态缓存初始化为 null,在构造函数中检查它是否为 null,如果不是,则构建并设置它。IE:
class TestBase
{
private static ConcurrentDictionary<string, PropertyInfo> Cache;
protected TestBase()
{
if(Cache == null)
{
ConcurrentDictionary<string, PropertyInfo> cache =
new ConcurrentDictionary<string, PropertyInfo>();
// Populate...
Cache = cache;
}
}
}
这有一个缺陷,如果我在第一个对象仍在填充缓存时构造另一个对象,我最终将构建两个缓存,而第二个将(可能,但不总是)覆盖第一个。这可能没问题,因为它们都是完整的缓存,但它看起来很老套,我希望我们能做得更好。
所以我的第二个想法是在静态构造函数中初始化缓存,在创建任何实例之前每个 AppDomain 只调用一次。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 (或类似的)来假设。