Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Initialize a Thread-Safe Static Cache of Reflection Data

I've been pondering this for a couple hours and I'm a little surprised at how challenging it is. I'm trying to initialize a static cache of reflection data for a given object from a base class that will be accessed from multiple threads. I'm having a hard time coming up with the correct pattern for initializing the cache.

My first thought was that I'll just initialize the static cache to null, check if it's null in the constructor, and then build and set it if it's not. I.e.:

class TestBase
{
  private static ConcurrentDictionary<string, PropertyInfo> Cache;

  protected TestBase()
  {
    if(Cache == null)
    {
      ConcurrentDictionary<string, PropertyInfo> cache =
        new ConcurrentDictionary<string, PropertyInfo>();
      // Populate...
      Cache = cache;
    }
  }
}

This has a flaw in that if I construct another object while the first one is still populating the cache, I'll end up constructing two caches and the second will (presumably, though not always) overwrite the first. This is probably okay since they'll both be complete caches, but it seems hacky and I would hope we can do better.

So my second thought was to initialize the cache in a static constructor which only gets called once per AppDomain before any instances are created. StackOverflow seems to have several answers to similar questions that point in this direction. This seemed great until I realized that the static constructor won't have access to the reflection data for the derived type.

I could always synchronize access in the constructor to insure only one thread is creating/populating the cache and any other access while that's happening should block, but then I'm locking on every construction just to protect an operation that should only happen once. I don't like the performance implications of that.

What I have right now is a flag that is set using Interlocked.Exchange and a ManualResetEventSlim. It looks like this:

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();
  }
}

I suspect that there might already be an accepted, or at least known, way of doing this kind of thing - is this it? If not, is there a better way to synchronize the cache initialization? Note that the question is not about how to make cache access thread-safe, that can be assumed by using ConcurrentDictionary (or similar).

like image 564
daveaglick Avatar asked Mar 07 '26 19:03

daveaglick


1 Answers

You can use the Lazy<T> class to lazily initialize something in a thread safe manner without writing your own plumbing.

If you want to eagerly cache reflection data, you'll need to use Assembly.GetTypes() to scan for compatible types (e.g. ones that are decorated with a certain attribute). For example:

var types = typeof(TestBase).Assembly.GetTypes().Where(type => --some condition--);
like image 77
Allon Guralnek Avatar answered Mar 09 '26 07:03

Allon Guralnek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!