【发布时间】:2019-12-16 03:59:53
【问题描述】:
如何对键可能在也可能不在 ConcurrentDictionary 中的项执行原子操作?
例如,假设我想使用特定键重置项目的一些状态值,它可能尚未在字典中,也可能不在字典中。我不想重置所有状态值,因此不能简单地用新实例替换原始项目。
这是一个与我的用例非常相似的人为示例。您可以假设类和方法包含更多不会影响手头问题的代码。
public class MyManager
{
private ConcurrentDictionary<int, MyWorker> _dict
= new ConcurrentDictionary<int, MyWorker>();
public void DoSomething1()
{
// ...
_dict.GetOrAdd(1, new MyWorker(Guid.NewGuid()));
// ...
}
public void DoSomething2()
{
// ...
_dict.TryRemove(1, out MyWorker worker);
// ...
}
public void DoSomething3()
{
// OPTION 1: Won't it be possible for a different thread to remove
// the object before the Reset() call?
var myWorker = _dict.GetOrAdd(1, new MyWorker(Guid.NewGuid()));
myWorker.Reset();
// OPTION 2: Does method chaining somehow guarantee execution within
// the same lock?
_dict.GetOrAdd(1, new MyWorker(Guid.NewGuid())).Reset();
// OPTION 3: ?
}
}
public class MyWorker
{
private int _someValue = 1;
private IList<string> _someList = new List<string>();
private Guid _immutableValue; // should not get reset
public MyWorker(Guid immutableValue)
{
_immutableValue = immutableValue;
}
public void Reset()
{
_someValue = 1;
_someList = new List<string>();
// NB. do not change _immutableValue!
}
}
在此示例中,不同的线程可能会以任意顺序多次调用 DoSomething1() 或 DoSomething2(),这可能会导致字典中键为“1”的 MyWorker 随时被替换。
我需要 DoSomething3() 来调用 MyWorker.Reset() 并知道如果字典中已经存在该对象,则将使用该对象。
- 选项 1 允许在调用 Reset 之前将检索到的 MyWorker 替换为新实例。
- 选项 2 似乎会做同样的事情,除非在流畅使用时有特殊处理?
还有哪些其他选项可以检索一个值并在释放锁之前检索它的同一个锁中对其执行方法/操作?换句话说,如何保证原子操作?我可以以某种方式传入 lambda 吗?
【问题讨论】:
-
听起来这可能是一个有趣的问题,但我不知道你实际问的是什么。您能否解释一下您正在解决什么问题以及出了什么问题?
-
你签出
AddOrUpdate了吗? -
@yaakov
AddOrUpdate似乎没有重载,它允许在添加/更新对象的同一锁定范围内对添加/更新对象运行方法。唯一可用的重载允许自定义工厂生成唯一键值。 -
@Enigmativity 我需要对
ConcurrentDictionary中的一个项目进行操作(在示例中,调用Reset())。如果我获得了对该项目的引用,我只能拨打Reset()作为第二次呼叫。在多线程运行时环境中,其他一些线程可以在检索之后但在调用Reset()之前更新甚至删除项目。最终结果是Reset()可能无法按预期执行,因为它正在对一个对象进行操作,该对象要么:a)被替换(我只有对旧对象的引用),要么 b)以现在的方式更改拨打电话不合适。 -
@Mellevsen - 这不是您要解决的问题,而是您为解决问题而编写的代码。您的业务问题是什么?
标签: c# concurrency thread-safety atomic