发现问题

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

                using (var db = new SmsEntities())
{
Parallel.For(, , (i) =>
{
db.MemberCard.Add(new MemberCard()
{
CardNo = "NO_" + i.ToString(),
Banlance = ,
CreateTime = DateTime.Now,
Name = "Test_" + i.ToString(),
Status =
});
});
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(, , (i) =>
{
list.Add(new MemberCard()
{
CardNo = "NO_" + i.ToString(),
Banlance = ,
CreateTime = DateTime.Now,
Name = "Test_" + i.ToString(),
Status =
});
});
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)后,去繁从简,回归原始,用最简单直白的写法单线程循环来完成。虽然一番折腾下来还是回到最初,但是这过程中让我发现了意料之外问题,然后找到了原因,然后测试验证,最终得到了最优解决方案。还是那句话,填完坑,你就比之前更强大了!

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

  1. eclipse中tomcat的add and Remove找不到项目

    在我们运行项目前,都需要将项目部署到tomcat上,但是有时我们会遇到这种情况:项目明明存在,但是eclipse中tomcat的add and remove找不到项目,无法部署,那么这个问题该如何解决 ...

  2. 编写高质量代码改善C#程序的157个建议——建议83:小心Parallel中的陷阱

    建议83:小心Parallel中的陷阱 Parallel的For和ForEach方法还支持一些相对复杂的应用.在这些应用中,它允许我们在每个任务启动时执行一些初始化操作,在每个任务结束后,又执行一些后 ...

  3. 最近调试HEVC中码率控制, 发现HM里面一个重大bug

    最近调试HEVC中码率控制, 发现里面一个重大bug! 码率控制中有这么一个函数: Int TEncRCGOP::xEstGOPTargetBits( TEncRCSeq* encRCSeq, Int ...

  4. 【安全性测试】Android测试中的一点小发现

    在执行某个项目中的APP测试发现的两个问题,自然也是提供参考,作为经验记录下来. 一.通过apk的xml文件获取到某项目APP的账号和密码 使用eclipsel或者drozer,获得apk的xml文件 ...

  5. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  6. seata服务端和客户端配置(使用nacos进行注册发现,使用mysql进行数据持久化),以及过程中可能会出现的问题与解决方案

    seata服务端和客户端配置(使用nacos进行注册发现,使用mysql进行数据持久化),以及过程中可能会出现的问题与解决方案 说明: 之所以只用nacos进行了注册与发现,因为seata使用naco ...

  7. 在IntelliJ IDEA中添加框架支持时找不到Hibernate的解决办法

    问题描述 第一次在Add Frameworks support界面中添加hibernate支持的时候,异常中断,导致没有成功添加. 第二次进入Add Frameworks support窗口时,发现找 ...

  8. 无法启动此程序,因为计算机中丢失AdbWinApi.dll。尝试重新安装该程序以解决此问题

    第一次搭建android开发环境,装完adb以后,打开DOS验证安装是否成功:但输入adb logcat调试时,系统弹出以下异常的对话框: 无法启动此程序,因为计算机中丢失AdbWinApi.dll. ...

  9. rpm包安装过程中依赖问题“libc.so.6 is needed by XXX”解决方法

    rpm包安装过程中依赖问题"libc.so.6 is needed by XXX"解决方法 折腾了几天,终于搞定了CentOS上的Canon LBP2900打印机驱动.中间遇到了一 ...

随机推荐

  1. 解决ASP.NET上传文件大小限制

    第一种方法,主要适用于IIS6.0版本 一.修改配置Web.Config文件中的httpRuntime节点对于asp.net,默认只允许上传4M文件,增加如下配置,一般可以自定义最大文件大小.一.修改 ...

  2. 使用WCF传输DataTable:DataTable和Xml格式的字符串相互转换(C#)

    背景:项目中要用到客户端向服务端传数据,使用WCF,绑定webHttpBinding,做了一个小例子. 业务逻辑简介:客户端在a表中添加了几条数据,从SQL Server数据库直接取出新添加的数据(D ...

  3. 《Redis入门指南(第二版)》读书思考总结之Redis五大数据类型

    热身:系统级命令 1. 获得符合规则的键名列表 KEYS pattern 模式匹配 产品的缓存:product+"."+....;  => keys product* 订单的 ...

  4. 【WPF】新复制wpf项目报错

    错误提示:Program does not contain a static 'Main' method suitable for an entry point 1.App.xaml 文件属性:生成操 ...

  5. js获取可视区域高度

    document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> BODY对象高度 document.docume ...

  6. C#事务

    看了很多关于事务的概念,还是觉得维基百科上说的最好: 数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成. 一个数据库事务通常包含了一个序列的对数据库的读 ...

  7. vi学习总结

    1.模式 命令行模式:光标的移动.内容删除移动复制操作 插入模式:文字输入,即编辑状态 底行模式:文件保存或退出vi,设置编辑环境 2.基本操作 vi myfile,输入vi 文件名,,则进入vi. ...

  8. phylogeny analysis

    Multiple Alignment: MUSCLE ProbCons T-Coffee ClustalW Alignment curation: Gblocks Remove positions w ...

  9. POJ2096 Collecting Bugs

    Time Limit: 10000MS   Memory Limit: 64000K Total Submissions: 5090   Accepted: 2529 Case Time Limit: ...

  10. iOS 10.0适配之旅

    1.升级Xcode体验 升级到Xcode之后,调试程序好多东西都不是太适应 控制台莫名给你打印一堆不是太好理解的东西 之前使用 Alcatraz 下载的插件都不能用(如何使用Alcatraz) 打开麦 ...