前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dotnet 警惕 ConcurrentDictionary 使用 FirstOrDefault 获取到非预期的首项

dotnet 警惕 ConcurrentDictionary 使用 FirstOrDefault 获取到非预期的首项

作者头像
林德熙
发布2023-06-18 16:04:57
2410
发布2023-06-18 16:04:57
举报
文章被收录于专栏:林德熙的博客

在 dotnet 里面的 ConcurrentDictionary 是一个支持并发读写的线程安全字典,在这个字典里面有一些行为会出现随机性,即多次执行相同的代码返回的结果可能不相同。本文记录在 ConcurrentDictionary 使用 FirstOrDefault 获取到非预期的首项的问题

在 dotnet 里面,无论是对 List 列表,还是 Dictionary 字典等获取首项,使用 FirstOrDefault 总是可以获取到第一个加入到集合或字典里面的元素。然而这个行为在 ConcurrentDictionary 里面是不成立的。在 ConcurrentDictionary 里面如果使用 FirstOrDefault 方法,则随机获取到字典里面的一项,但对相同的一个 ConcurrentDictionary 对象多次调用 FirstOrDefault 方法,在不更改 ConcurrentDictionary 内容的情况下,可以稳定获取到相同的首项元素对象

简单来说就是在 ConcurrentDictionary 里面,调用 FirstOrDefault 方法,不能保证获取到的对象就是第一个加入到 ConcurrentDictionary 字典里面的对象

如以下代码例子

代码语言:javascript
复制
using System.Collections.Concurrent;

for (int i = 0; i < int.MaxValue; i++)
{
    var dictionary = new ConcurrentDictionary<Foo, int>();
    dictionary.TryAdd(new Foo(), i);
    dictionary.TryAdd(new Foo(), i + 1);

    var first = dictionary.FirstOrDefault();
    if (first.Value != i)
    {
        // 证明首个不是第一个加入的
        Console.WriteLine($"首个不是第一个加入的");
        return;
    }
}

class Foo
{
    public Foo()
    {
        Number = _count;
        _count++;
    }

    private static int _count;

    public int Number { get; }
}

以上代码在一个大的循环里面,每次循环都创建一个字典,在给字典加入两个元素,最后加入的元素设置为和循环次数不相同的值,通过此可以用来在后续调用 FirstOrDefault 时判断获取到的元素是否首个加入字典的元素

运行代码可以看到,使用 FirstOrDefault 获取到的元素,不是第一个加入字典的元素。同时,多次运行代码,可以看到进入 if (first.Value != i) 不等于条件时的循环次数也会不相同,这就可以证明使用 FirstOrDefault 的执行结果比较随机

具体原理是在 ConcurrentDictionary 里面需要维护一个 Table 字典,字典里面存放的顺序和传入的 Key 对象的 Hash 有关,调用 FirstOrDefault 方法时获取到的是里面的 Table 字典的按照内存空间顺序的首项

由此原理即可知道,使用 FirstOrDefault 获取 ConcurrentDictionary 的首现是无法确保获取到的是首个加入字典的元素对象。同时如果在 ConcurrentDictionary 字典发生变更,比如不断加入值时,将导致调用 FirstOrDefault 无法稳定返回相同的对象

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

代码语言:javascript
复制
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 623c123b1d5e5669c5321c846d72c09e042135a6

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

代码语言:javascript
复制
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 623c123b1d5e5669c5321c846d72c09e042135a6

获取代码之后,进入 CerdearhachiRairwainalnearyal 文件夹

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档