服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决思路详解

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

2021-12-09 13:44balahoho C#

这篇文章主要介绍了在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决过程的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

发现问题

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}

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

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

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

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

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

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

4、 一个 edmtype 不能多次映射到 clr 类。edmtype“smsmodel.membercard”映射了一次以上。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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(真实项目中的循环次数是手动输入的),点按钮提交,嗯,又吃瓜般的异常了…:

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

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

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

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

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

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

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

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

寻找解决方案并验证结论

也想过用partitioner分区来做,但是仔细一想,虽然分区内部是单线程,但是区与区之间还是多线程的,如果分的太细也就失去了parallel的意义,只得另寻出路。还好framework为我们也提供了一些线程安全的泛型集合(比如concurrentbag、concurrentqueue等),不过其本质还是用了锁,于是就综合做了一下单线程list、多线程list加锁、多线程concurrentbag、多线程concurrentqueue的性能对比,结果如下:

循环1000次时:

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

循环10000次时:

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

循环100000次时:

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

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

解决问题

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

原文链接:http://www.cnblogs.com/hohoa/p/6060228.html

延伸 · 阅读

精彩推荐
  • C#聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题,新手速来围观,一个通俗易懂的例子帮助大家更好的理解C#接口问题,感兴趣的小伙伴们可以参考一下...

    zenkey7072021-12-03
  • C#c#学习之30分钟学会XAML

    c#学习之30分钟学会XAML

    一个界面程序的核心,无疑就是界面和后台代码,而xaml就是微软为构建应用程序界面而创建的一种描述性语言,也就是说,这东西是搞界面的...

    C#教程网8812021-12-10
  • C#浅谈C# winForm 窗体闪烁的问题

    浅谈C# winForm 窗体闪烁的问题

    下面小编就为大家带来一篇浅谈C# winForm 窗体闪烁的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C#教程网7962021-12-21
  • C#C#直线的最小二乘法线性回归运算实例

    C#直线的最小二乘法线性回归运算实例

    这篇文章主要介绍了C#直线的最小二乘法线性回归运算方法,实例分析了给定一组点,用最小二乘法进行线性回归运算的实现技巧,具有一定参考借鉴价值,需要...

    北风其凉8912021-10-18
  • C#C# 后台处理图片的几种方法

    C# 后台处理图片的几种方法

    本篇文章主要介绍了C# 后台处理图片的几种方法,非常具有实用价值,需要的朋友可以参考下。...

    IT小伙儿10162021-12-08
  • C#Unity3D UGUI实现缩放循环拖动卡牌展示效果

    Unity3D UGUI实现缩放循环拖动卡牌展示效果

    这篇文章主要为大家详细介绍了Unity3D UGUI实现缩放循环拖动展示卡牌效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参...

    诗远3662022-03-11
  • C#C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    这篇文章主要介绍了C#实现的文件操作封装类,结合完整实例形式分析了C#封装文件的删除,移动,复制,重命名等操作相关实现技巧,需要的朋友可以参考下...

    Rising_Sun3892021-12-28
  • C#C#基础之泛型

    C#基础之泛型

    泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。接下来通过本文给大家介绍c#基础之泛型,感兴趣的朋友一起学习吧...

    方小白7732021-12-03