专栏首页用户5447549的专栏在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决过程

在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决过程

发现问题

需求很简单,大致就是要批量往数据库写数据,于是打算用Parallel并行的方式写入,希望能利用计算机多核特性加快程序执行速度。想的很美好,于是快速撸了类似下面的一串代码:

                using (var db = new SmsEntities())
                {
                    Parallel.For(0, 1000, (i) =>
                    {
                        db.MemberCard.Add(new MemberCard()
                        {
                            CardNo = "NO_" + i.ToString(),
                            Banlance = 0,
                            CreateTime = DateTime.Now,
                            Name = "Test_" + i.ToString(),
                            Status = 1
                        });
                    });
                    db.SaveChanges();
                }

可意外的是竟然无情的报错了:

奇葩的是当我再次刷新的时候异常又不一样了,于是连着刷新好多次,总结出现过的异常有下面这些:

1、 未将对象引用设置到对象的实例。

2、 已添加了具有相同键的项。

3、 集合已修改;可能无法执行枚举操作。

4、 一个 EdmType 不能多次映射到 CLR 类。EdmType“SmsModel.MemberCard”映射了一次以上。

其中1和2是出现最多的,而且所有异常都是出现在Add的时候,各种吃瓜表情~没办法,接着一一断点调试,还是没找出原因,出于进度考虑,换成了另一种方案,也就是用DbSet的AddRange方法。先在Parallel中累加出一个实体List,然后一次性添加到DbSet中,代码演变为:

            List<MemberCard> list = new List<MemberCard>();
            using (var db = new SmsEntities())
            {
                var result = Parallel.For(0, 1000, (i) =>
                  {
                      list.Add(new MemberCard()
                      {
                          CardNo = "NO_" + i.ToString(),
                          Banlance = 0,
                          CreateTime = DateTime.Now,
                          Name = "Test_" + i.ToString(),
                          Status = 1
                      });
                  });
                if (result.IsCompleted)
                {
                    db.MemberCard.AddRange(list);
                    db.SaveChanges();
                }
            }

然后编译、测试,没问题,就先放着了。

分析问题

第二天到公司心里还在纠结这个问题,于是打开页面输入生成的数据量1000(真实项目中的循环次数是手动输入的),点按钮提交,嗯,又吃瓜般的异常了…:

心想昨天测试都好好的啊(其实昨天输入的是10,心虚脸...),没办法,上断点吧,一看吓一跳:

明明循环1000次,结果只有971条数据,而且里面还有为null的,经过多次调试发现这是一个随机现象,Count是随机的null也是随机的,有时出现有时没有,初步判断这是一个在多线程情况下引发的一个资源调配异常。So,上MSDN看了一下List的介绍,最后面“线程安全”写着:

一切貌似都清楚了,于是打算验证一下结果,加上了锁,测试结果为:

list里面也没有再出现null了,确认是因为多线程安全引起的异常。于是想起昨天那个问题是否也是同样的问题,再上MSDN搜了一下DbContext类和DbSet类,都是这样说的:

接着就给dbcontext上了锁,测试,这次总算如我所料,完美运行。但是不解的是最初那几个异常是如何产生的,List中虽然数量不够也存在为null的对象,但是并没有直接爆出异常。现在只知道是线程问题,再详细的也搞不清楚,有知道的大神还麻烦指点一下。

寻找解决方案并验证结论

也想过用Partitioner分区来做,但是仔细一想,虽然分区内部是单线程,但是区与区之间还是多线程的,如果分的太细也就失去了Parallel的意义,只得另寻出路。还好Framework为我们也提供了一些线程安全的泛型集合(比如ConcurrentBag、ConcurrentQueue等),不过其本质还是用了锁【这里更正下错误:本质并不是用锁而是原子操作,感谢评论中的园友指正】,于是就综合做了一下单线程list、多线程list加锁、多线程ConcurrentBag、多线程ConcurrentQueue的性能对比,结果如下:

循环1000次时:

循环10000次时:

循环100000次时:

  • 得出结论就是,在执行次数超大时用线程安全类型会更慢,在执行次数较少时线程安全类型也没什么优势。
  • List和DbSet是非线程安全的。

解决问题

最后在经过仔细测试验证和考虑项目实际需求(几乎不可能一次10000)后,去繁从简,回归原始,用最简单直白的写法单线程循环来完成。虽然一番折腾下来还是回到最初,但是这过程中让我发现了意料之外问题,然后找到了原因,然后测试验证,最终得到了最优解决方案。还是那句话,填完坑,你就比之前更强大了!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • .NetCore&Linux&Docker&Portainer踩坑历险记

    最近有一个云服务器和数据库的迁移任务,踩坑爬坑无数次,觉得必须要记录一下。大家瓜子花生准备好,听我慢慢讲故事#手动笑哭#。

    HOHO
  • 在Centos中部署redis运行状态图形化监控工具 — RedisLive

    前两天看到张善友老师的一篇文章《先定个小目标, 使用C# 开发的千万级应用》,里面给出了一张腾讯OA基础服务中redis运行情况的一张监控图,然后想到自己的项目...

    HOHO
  • 【新手总结】在.Net项目中使用Redis作为缓存服务

    最近由于项目需要,在系统缓存服务部分上了redis,终于有机会在实际开发中玩一下,之前都是自己随便看看写写,很零碎也没沉淀下来什么,这次算是一个系统学习和实践过...

    HOHO
  • 【译】Go 语言源码贡献官方指导文档

    这是一份指导你完成向 Go 语言项目贡献代码整个流程的文档,会略微跟其他开源项目所使用的指导文档有所不同。我们假设阅读者已经对 Git 和 Go 有基本的理解以...

    潘少
  • 【AI发展出人类无法理解的语言】Facebook关闭“失控” AI 项目

    【新智元导读】 Facebook上个月引起广泛争议的“开发出人类无法理解”的沟通的智能对话机器人项目已经被关闭,因为研究者担心会“失控”。 根据digitalj...

    新智元
  • CRM product ID format相关配置

    Storage form of product ID not yet defined in Customizing Message No. COM PRODU...

    Jerry Wang
  • 搭建一个泰拉瑞亚手机版服务器

    于是mobile端找了我好久终于在 https://forums.terraria.org/index.php?threads/terraria-mobile-...

    iVampireSP.com
  • Python 为什么会有个奇怪的“...”对象?

    在写上一篇《Python 为什么要有 pass 语句?》时,我想到一种特别的写法,很多人会把它当成 pass 语句的替代。在文章发布后,果然有三条留言提及了它。

    Python猫
  • 生信宝典之傻瓜式 (三) 我的基因在哪里发光 - 如何查找基因在发表研究中的表达

    还在为不会分析大数据发愁吗? 还在为无法查询和比较发表文章中感兴趣基因表达值抱怨吗? 使用genevestigator,高效利用已经有研究结果,轻松与同行研究结...

    生信宝典
  • python3–内置模块

    老七Linux

扫码关注云+社区

领取腾讯云代金券