3

我已经思考了几个小时,我对它的挑战性感到有点惊讶。我正在尝试从将要从多个线程访问的基类中为给定对象初始化反射数据的静态缓存。我很难想出初始化缓存的正确模式。

我的第一个想法是,我只需将静态缓存初始化为 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 (或类似的)来假设。

4

2 回答 2

3

您可以使用Lazy<T>该类以线程安全的方式懒惰地初始化某些东西,而无需编写自己的管道。

如果您想急切地缓存反射数据,则需要使用Assembly.GetTypes()扫描兼容类型(例如,用特定属性修饰的类型)。例如:

var types = typeof(TestBase).Assembly.GetTypes().Where(type => --some condition--);
于 2012-04-30T17:01:47.933 回答
0

您应该在构造函数 ( private static ConcurrentDictionary<string, PropertyInfo> Cache = new ConcurrentDictionary<string, PropertyInfo>();) 之外创建字典并检查 Count 以确定是否需要构建缓存。然后,如果您TryAdd在构建缓存时使用执行添加操作,则根本不需要执行任何自己的线程锁定。

您可以处理您在评论中提出的问题,只需使用另一个属性 IsCahceValid 来指示缓存已完成。由于您将使用 TryAdd ,因此您甚至不需要锁定变量,因为在初始完成的任何时候将其设置为 true 都可以正常工作。

于 2012-04-30T16:58:29.853 回答