在ConcurrentDictionary的AddOrUpdate方法中,更新部分应该添加什么内容?

134
我正在尝试使用ConcurrentDictionary来重新编写一些代码。我已经查看了一些示例,但在实现AddOrUpdate函数时仍然遇到了困难。以下是原始代码:
    dynamic a = HttpContext;
    Dictionary<int, string> userDic = this.HttpContext.Application["UserSessionList"] as Dictionary<int, String>;

   if (userDic != null)
   {
      if (useDic.ContainsKey(authUser.UserId))
      {
        userDic.Remove(authUser.UserId);
      }
   }
  else
  {
     userDic = new Dictionary<int,string>();
  }
  userDic.Add(authUser.UserId, a.Session.SessionID.ToString());
  this.HttpContext.Application["UserDic"] = userDic;

我不知道在更新部分要添加什么内容。
userDic.AddOrUpdate(authUser.UserId,
                    a.Session.SessionID.ToString(),
                    /*** what to add here? ***/);

任何指点都将不胜感激。
5个回答

257
你需要传递一个返回要存储在字典中的值的Func,以便在更新时使用。我猜在你的情况下(因为你不区分添加和更新),这应该是:
var sessionId = a.Session.SessionID.ToString();
userDic.AddOrUpdate(
  authUser.UserId,
  sessionId,
  (key, oldValue) => sessionId);

也就是说,Func 总是返回 sessionId,因此 Add 和 Update 都设置相同的值。

顺便提一下,在 MSDN 页面上有一个示例。


7
我一直在苦苦寻找一个函数,用于将数值相加或更新为相同的值。谢谢。 - Zapnologica
3
好的回答。仅从在Visual Studio中显示的AddOrUpdate()签名中,您只能猜测2个参数的含义。但是在@user438331询问的具体情况下,我认为使用简单的索引器在我的答案中给出的解决方案更好。 - Niklas Peter
8
正如 @NiklasPeter 所指出的(https://dev59.com/SWw05IYBdhLWcg3wykzn#32796165),你最好只使用普通索引器来覆盖值,因为在你的情况下,如果有任何现有的值都没有意义。这样更易读。 - Rory
6
我建议修改你的回答,指向 @NiklasPeter 的回答。那是一个更好的解决方案。 - Will Calderwood
与指向其他答案的建议相反,这似乎是正确的答案,请查看https://dev59.com/AlgQ5IYBdhLWcg3w7oVA#42013356。 - Christian

85

我希望我没有在你的问题中漏掉什么,但为什么不这样做呢?它更容易、原子化并且是线程安全的(请参见下文)。

userDic[authUser.UserId] = sessionId;
使用索引器的 setter 将键值对无条件地存储到字典中,如果该键已经存在,则覆盖任何该键的值。 (参见:http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx
索引器也是原子操作的。如果传递的是一个函数,则可能不是原子操作:
所有这些操作都是原子操作,并且在涉及ConcurrentDictionary上的所有其他操作方面都是线程安全的。每个操作的唯一原子性限制是那些接受委托的操作,即AddOrUpdate和GetOrAdd。[...]这些委托在锁之外调用。
(参见:http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx

6
是的,原子操作一次性完成,不能被中断或分割。然而,它并不安全,因为其他人可以在你之前将其更改为其他内容,这种情况下他们的更改会丢失,而你也不知道发生了什么。如果你只想在值符合预期时进行更改,则原子操作无法满足你的需求。 - trampster

37

我最终实现了一个扩展方法:

static class ExtensionMethods
{
    // Either Add or overwrite
    public static void AddOrUpdate<K, V>(this ConcurrentDictionary<K, V> dictionary, K key, V value)
    {
        dictionary.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
    }
}

7
为什么原始实现没有这样的方法? - mcmillab
根据Niklas Peter在上面的回复和短语“每个操作的唯一注意事项是那些接受委托的操作”,这个扩展方法是不是原子正确的呢?非常感谢! - fdhsdrdark

1

对于那些感兴趣的人,我目前正在实现一个案例,这是一个很好的例子,可以使用“oldValue”即现有值而不是强制使用新值(就个人而言,我不喜欢“oldValue”这个术语,因为它并不是很旧,仅仅是在几个处理器时钟周期内从并行线程中创建)。

dictionaryCacheQueues.AddOrUpdate(
    uid,
    new ConcurrentQueue<T>(),
    (existingUid, existingValue) => existingValue
);

7
如果你不想改变现有的值,应该使用 GetOrAdd()。更多信息请参考https://msdn.microsoft.com/en-us/library/ee378674(v=vs.110).aspx。 - Rory
1
嗯,是的,你说得对,GetOrAdd()在这种情况下更简单并且足够了 - 谢谢你的提示! - Nicolas

0

最简单的2023解决方案,不引入额外的新变量:

userDic.AddOrUpdate(authUser.UserId,
                    a.Session.SessionID.ToString(),
                    (_, _) => a.Session.SessionID.ToString());

请享用!

附言:了解有关丢弃变量的on MSDN


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接