I'm writing a decorator to implement caching. The object will be registered as a singleton by my DI container. Because I know I'm registering my object as a singleton, the field representing my cache is not static. I'm not sure if this is best practice, but I'm trying to avoid locking at all cost. My cache is lazily initialized and an expensive/run only once operation. My question is do I need to worry about multiple threads running the cache initialization logic? My gut tells me "yes I do need to worry", but I've heard other devs say "No point in locking if it's not static".
//SimpleInjector DI Container configuration
public static class Bootstrapper
{
public static void ConfigureContainer(Container container)
{
container.Register<IQueryHandler<GetFoos, Foo[]>, GetFoosHandler>(Lifestyle.Singleton);
container.RegisterDecorator<IQueryHandler<GetFoos, Foo[]>, GetFoosCachingHandler>(Lifestyle.Singleton);
}
}
public class Foo
{
public int Id;
public string FooTypeCode;
public string Name;
}
public class GetFoos : IQuery<Foo[]>
{
public string FooTypeCode;
}
public class GetFoosCachingHandler : IQueryHandler<GetFoos, Foo[]>
{
private Lazy<Dictionary<string, Foo[]>> _cache;
private readonly IQueryHandler<GetFoos, Foo[]> _queryHandler;
public GetFoosCachingHandler(IQueryHandler<GetFoos, Foo[]> queryHandler)
{
_queryHandler = queryHandler;
_cache = new Lazy<Dictionary<string, Foo[]>>(() =>
{
//expensive and run only once operation e.g. subscribe to bus for cache invalid messages and reset cache
return new Dictionary<string, Foo[]>();
});
}
public Foo[] Handle(GetFoos query)
{
var cache = _cache.Value;
if (!cache.ContainsKey(query.FooTypeCode))
{
cache[query.FooTypeCode] = _queryHandler.Handle(new GetFoos { FooTypeCode = query.FooTypeCode });
}
return cache[query.FooTypeCode];
}
}
Yes, you need locking to prevent multiple threads from running the same code.
"No point in locking if it's not static"
That only applies when each thread has its own instance of a class. As soon as you share an instance between threads, you need to synchonise the access.
Guffa is right. Nothing to add at this point. What I would like to add is a little refactoring. You should probably extract the caching behavior from the decorator as follows:
public class GetFoosCachingHandler : IQueryHandler<GetFoos, Foo[]>{
private readonly ICache _cache;
private readonly IQueryHandler<GetFoos, Foo[]> _queryHandler;
public GetFoosCachingHandler(ICache cache, IQueryHandler<GetFoos, Foo[]> queryHandler){
_cache = cache;
_queryHandler = queryHandler;
}
public Foo[] Handle(GetFoos query) {
var result = _cache.Load<Foo[]>(query.FooTypeCode);
if (result == null) {
_cache.Store<Foo[]>(query.FooTypeCode, result = _queryHandler.Handle(query));
}
return result;
}
}
A few things to note here:
Dictionary<,>
is really lightweight and the dictionary is always created. Foo
is and array is), making it dangerous to reuse them on multiple threads (you never know who changes them).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.