如果没有静态字段,我是否需要担心对象是线程安全的?

时间:2015-08-20 19:50:09

标签: c# dependency-injection thread-safety singleton

我正在编写一个装饰器来实现缓存。该对象将由我的DI容器注册为单例。因为我知道我将对象注册为单例,所以表示我的缓存的字段不是静态的。我不确定这是否是最佳做法,但我试图不惜一切代价避免锁定。我的缓存是懒惰的初始化,只有一次昂贵/运行。我的问题是我需要担心运行缓存初始化逻辑的多个线程吗?我的直觉告诉我“是的,我确实需要担心”,但我听到其他开发人员说“如果它不是静止的话,锁定没有意义”。

//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];
    }
}

2 个答案:

答案 0 :(得分:5)

是的,您需要锁定以防止多个线程运行相同的代码。

  

“如果它不是静态的话,没有锁定点”

仅当每个线程都有自己的类实例时才适用。只要在线程之间共享实例,就需要同步访问权限。

答案 1 :(得分:1)

Guffa是对的。此时无需添加任何内容。我想补充的是一点点重构。您应该从装饰器中提取缓存行为,如下所示:

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

这里需要注意的一些事项:

  • 装饰者不应该创建查询,而只是将传入的查询消息传递给它的decoratee。
  • 你可能想要创建一个通用的缓存装饰器;这允许您将其应用于多个处理程序。
  • 使用Lazy是没用的,因为创建Dictionary<,>非常轻量级,并且始终会创建字典。
  • 如果您使装饰器通用,则需要一种不同的方法来确定缓存键。过去对我来说非常棒的是将完整的查询消息序列化为JSON(使用JSON.NET)并将其用作密钥。您可能希望对结果执行相同的操作,因为这些结果是可变对象(Foo和数组都是),这使得在多个线程上重用它们很危险(您永远不知道谁更改了它们)。