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

C# 如果一个对象没有静态字段,我是否需要担心它是线程安全的?,c#,dependency-injection,thread-safety,singleton,C#,Dependency Injection,Thread Safety,Singleton,我正在编写一个decorator来实现缓存。该对象将由我的DI容器注册为单例。因为我知道我将对象注册为单例,所以表示缓存的字段不是静态的。我不确定这是否是最佳实践,但我会不惜一切代价避免锁定。我的缓存是延迟初始化的,并且是一个昂贵的/只运行一次的操作。我的问题是,我是否需要担心运行缓存初始化逻辑的多个线程?我的直觉告诉我“是的,我确实需要担心”,但我听到其他开发人员说“如果不是静态的,锁定没有意义” //SimpleInjector DI容器配置 公共静态类引导程序 { 公共静态无效配置容器(容

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

//SimpleInjector DI容器配置
公共静态类引导程序
{
公共静态无效配置容器(容器容器)
{
容器。登记(生活方式。单身);
容器。注册Decorator(生活方式。单身);
}
}
公开课Foo
{
公共int Id;
公共字符串类型码;
公共字符串名称;
}
公共类GetFoos:IQuery
{
公共字符串类型码;
}
公共类GetFoosCachingHandler:IQueryHandler
{
私有延迟缓存;
专用只读IQueryHandler\u queryHandler;
公共GetFoosCachingHandler(IQueryHandler-queryHandler)
{
_queryHandler=queryHandler;
_缓存=新延迟(()=>
{
//昂贵且仅运行一次操作,例如订阅总线以缓存无效消息和重置缓存
返回新字典();
});
}
公共Foo[]句柄(GetFoos查询)
{
var cache=\u cache.Value;
if(!cache.ContainsKey(query.FooTypeCode))
{
cache[query.FooTypeCode]=\u queryHandler.Handle(新的GetFoos{FooTypeCode=query.FooTypeCode});
}
返回缓存[query.FooTypeCode];
}
}

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

“如果不是静态的,则锁定没有意义”


这仅适用于每个线程都有自己的类实例的情况。一旦在线程之间共享实例,就需要同步访问。

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;
    }
}
公共类GetFoosCachingHandler:IQueryHandler{
专用只读ICache\u缓存;
专用只读IQueryHandler\u queryHandler;
公共GetFoosCachingHandler(ICache、IQueryHandler和queryHandler){
_缓存=缓存;
_queryHandler=queryHandler;
}
公共Foo[]句柄(GetFoos查询){
var result=\u cache.Load(query.FooTypeCode);
如果(结果==null){
_cache.Store(query.FooTypeCode,result=_queryHandler.Handle(query));
}
返回结果;
}
}
这里有几点需要注意:

  • 装饰者不应该创建查询,而应该将传入的查询消息传递给其装饰者
  • 您可能希望创建一个通用缓存装饰器;这允许您将其应用于多个处理程序
  • 使用Lazy是无用的,因为创建一个
    字典
    实际上是轻量级的,而且字典总是被创建的
  • 如果将decorator设置为泛型,则需要另一种确定缓存键的方法。过去对我来说非常有用的是将完整的查询消息序列化为JSON(使用JSON.NET)并将其用作键。您可能希望对结果执行相同的操作,因为这些结果是可变对象(既有
    Foo
    is,也有array is),因此在多个线程上重用它们是危险的(您永远不知道是谁更改了它们)

除非我完全误读了您的代码,否则它不是单身汉。每次创建GetFoosCachingHandler时都会设置_缓存,因此不需要锁定。现在,如果您确实有一个单例,那么您应该非常小心地设置_缓存,并用lock()将其包围,以确保线程安全。但实际上只需要执行一次lock(),因此没有真正的性能问题。哦,顺便说一下,我认为单例中的_缓存是静态的。我假设您希望所有呼叫者都获得相同的缓存。谢谢您的回复。当我在启动时配置DI容器时,我将其注册为单例。添加了上面的内容。很好。另一种方式也是如此-如果静态数据没有在线程之间共享,那么就不需要锁定。。。静态字段通常由多个线程访问这一事实并不意味着锁定或与静态字段无关。谢谢你的指导@Steven!是的,我需要使缓存结果不可变。如果我使我的查询也不可变和不可满足呢?你能想出我不想那样做的理由吗?我想我必须确保一个查询的所有属性都是不可变的。在C#中,使对象不可变是一项大量的工作。我认为这不值得麻烦。给每个请求一个新的反序列化对象要容易得多。或者在F#中定义所有查询和响应类型,并在C#中使用它们。两全其美。
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;
    }
}