C# 以线程安全的方式更改和读取ConcurrentDictionary中对象的属性
我使用ConcurrentDictionary在WebAPI应用程序中收集内存中的数据。我使用api方法在ConcurrentDictionary中添加和更新对象。还有一个后台线程根据对象属性分析和清理这个字典。现在我考虑两种方法: 1.在AddOrUpdate方法中的updateValueFactory中使用字典项锁定,但问题是如何正确读取属性,以确保我拥有它的最新版本,并且我没有在非稳定状态下读取属性C# 以线程安全的方式更改和读取ConcurrentDictionary中对象的属性,c#,multithreading,concurrency,C#,Multithreading,Concurrency,我使用ConcurrentDictionary在WebAPI应用程序中收集内存中的数据。我使用api方法在ConcurrentDictionary中添加和更新对象。还有一个后台线程根据对象属性分析和清理这个字典。现在我考虑两种方法: 1.在AddOrUpdate方法中的updateValueFactory中使用字典项锁定,但问题是如何正确读取属性,以确保我拥有它的最新版本,并且我没有在非稳定状态下读取属性 public class ThreadsafeService2 { private
public class ThreadsafeService2
{
private readonly ConcurrentDictionary<string, ThreadSafeItem2> _storage =
new ConcurrentDictionary<string, ThreadSafeItem2>();
public void AddOrUpdate(string name)
{
var newVal = new ThreadSafeItem2();
_storage.AddOrUpdate(name, newVal, (key, oldVal) =>
{
//use lock
lock (oldVal)
{
oldVal.Increment();
}
return oldVal;
});
}
public void Analyze()
{
foreach (var key in _storage.Keys)
{
if (_storage.TryGetValue(key, out var item))
{
//how to read it properly?
long ticks = item.ModifiedTicks;
}
}
}
}
public class ThreadSafeItem2
{
private long _modifiedTicks;
private int _counter;
public void Increment()
{
//no interlocked here
_modifiedTicks = DateTime.Now.Ticks;
_counter++;
}
//now interlocked here
public long ModifiedTicks => _modifiedTicks;
public int Counter => _counter;
}
这里的最佳实践是什么?我无法评论您具体在做什么,但联锁和并发字典比您自己做的锁更好
不过,我会质疑这种做法。您的数据足够重要,但不重要到可以保存它吗?根据应用程序的使用情况,这种方法会在一定程度上降低应用程序的速度。同样,由于不知道您正在做什么,您可以将每个添加都抛出到MSMQ中,然后按照某个计划运行一个外部exe来处理这些项目。该网站只需启动并忘记,没有线程要求。因此,您的两个实现都存在重大问题。第一个解决方案在递增时锁定,但在读取时不锁定,这意味着访问数据的其他位置可以读取无效状态 一个非技术性的问题,但仍然是一个主要的问题,就是您已经将类命名为ThreadSaveItem,但实际上它并不是设计为可以从多个线程安全访问的。在这个实现中,调用者的责任是确保不从多个线程访问该项。如果我看到一个名为ThreadSafeItem的类,我将假设从多个线程访问它是安全的,并且只要我执行的每个操作都是逻辑原子的,我就不需要同步对它的访问 您的联锁解决方案是有问题的,因为您必须修改要修改的字段,这些字段在概念上是绑定在一起的,但您没有将它们的更改同步在一起,这意味着有人可以观察到对其中一个字段的修改,而不是对另一个字段的修改,这是该代码的问题 其次,在这两种解决方案中使用AddOrUpdate并不合适。方法调用的全部要点是添加一个项或用另一个项替换它,而不是改变提供的项,这就是它接受返回值的原因;你应该生产一种新产品。如果您想使用获取可变项并对其进行变异的方法,那么可以调用GetOrAdd来获取现有项或创建新项,然后使用返回值以线程安全的方式对其进行变异 通过简单地使ThreadSafeItem不可变,整个解决方案从根本上得到了简化。它允许您在ConcurrentDictionary上使用AddOrUpdate进行更新,这意味着需要进行的唯一同步是更新ConcurrentDictionary的值,并且它已经处理了自身状态的同步,在访问ThreadSafeItem时根本不需要进行同步,因为对数据的所有访问本质上是线程安全的,因为它是不可变的。这意味着您实际上根本不需要编写任何同步代码,这正是您希望尽可能争取的 最后,我们有实际的代码:
public class ThreadsafeService3
{
private readonly ConcurrentDictionary<string, ThreadSafeItem3> _storage =
new ConcurrentDictionary<string, ThreadSafeItem3>();
public void AddOrUpdate(string name)
{
_storage.AddOrUpdate(name, _ => new ThreadSafeItem3(), (_, oldValue) => oldValue.Increment());
}
public void Analyze()
{
foreach (var pair in _storage)
{
long ticks = pair.Value.ModifiedTicks;
//Note, the value may have been updated since we checked;
//you've said you don't care and it's okay for a newer item to be removed here if it loses the race.
if (isTooOld(ticks))
_storage.TryRemove(pair.Key, out _);
}
}
}
public class ThreadSafeItem3
{
public ThreadSafeItem3()
{
Counter = 0;
}
private ThreadSafeItem3(int counter)
{
Counter = counter;
}
public ThreadSafeItem3 Increment()
{
return new ThreadSafeItem3(Counter + 1);
}
public long ModifiedTicks { get; } = DateTime.Now.Ticks;
public int Counter { get; }
}
我认为这取决于Analyze如何处理这些值。在阅读long ticks=item.ModifiedTicks之后使用第二种方法,此值可能已经过时,另一个线程可能已经在阅读后更新了它。取决于您如何使用这些值,这可能是一个问题,也可能不是。@Evk我将它与某个阈值进行比较,然后决定:是否从字典中删除它。此外,在工作项目中,我在ThreadSafeItem中还有一个方法,它根据计数器和modifiedticks值返回bool值。所以,如果您只增加修改的刻度,并根据modifiedticks>threshold删除项目,那么您就不关心过时的刻度值。另一方面,如果您基于ModifiedTicks
public class ThreadsafeService3
{
private readonly ConcurrentDictionary<string, ThreadSafeItem3> _storage =
new ConcurrentDictionary<string, ThreadSafeItem3>();
public void AddOrUpdate(string name)
{
_storage.AddOrUpdate(name, _ => new ThreadSafeItem3(), (_, oldValue) => oldValue.Increment());
}
public void Analyze()
{
foreach (var pair in _storage)
{
long ticks = pair.Value.ModifiedTicks;
//Note, the value may have been updated since we checked;
//you've said you don't care and it's okay for a newer item to be removed here if it loses the race.
if (isTooOld(ticks))
_storage.TryRemove(pair.Key, out _);
}
}
}
public class ThreadSafeItem3
{
public ThreadSafeItem3()
{
Counter = 0;
}
private ThreadSafeItem3(int counter)
{
Counter = counter;
}
public ThreadSafeItem3 Increment()
{
return new ThreadSafeItem3(Counter + 1);
}
public long ModifiedTicks { get; } = DateTime.Now.Ticks;
public int Counter { get; }
}