C# 如果一个对象没有静态字段,我是否需要担心它是线程安全的?
我正在编写一个decorator来实现缓存。该对象将由我的DI容器注册为单例。因为我知道我将对象注册为单例,所以表示缓存的字段不是静态的。我不确定这是否是最佳实践,但我会不惜一切代价避免锁定。我的缓存是延迟初始化的,并且是一个昂贵的/只运行一次的操作。我的问题是,我是否需要担心运行缓存初始化逻辑的多个线程?我的直觉告诉我“是的,我确实需要担心”,但我听到其他开发人员说“如果不是静态的,锁定没有意义”C# 如果一个对象没有静态字段,我是否需要担心它是线程安全的?,c#,dependency-injection,thread-safety,singleton,C#,Dependency Injection,Thread Safety,Singleton,我正在编写一个decorator来实现缓存。该对象将由我的DI容器注册为单例。因为我知道我将对象注册为单例,所以表示缓存的字段不是静态的。我不确定这是否是最佳实践,但我会不惜一切代价避免锁定。我的缓存是延迟初始化的,并且是一个昂贵的/只运行一次的操作。我的问题是,我是否需要担心运行缓存初始化逻辑的多个线程?我的直觉告诉我“是的,我确实需要担心”,但我听到其他开发人员说“如果不是静态的,锁定没有意义” //SimpleInjector 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)并将其用作键。您可能希望对结果执行相同的操作,因为这些结果是可变对象(既有
is,也有array is),因此在多个线程上重用它们是危险的(您永远不知道是谁更改了它们)Foo
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;
}
}