在Parallel中使用DbSet.Add()發(fā)現(xiàn)的一系列多線程問題和解決思路詳解
發(fā)現(xiàn)問題
需求很簡單,大致就是要批量往數(shù)據(jù)庫寫數(shù)據(jù),于是打算用Parallel并行的方式寫入,希望能利用計算機多核特性加快程序執(zhí)行速度。想的很美好,于是快速擼了類似下面的一串代碼:
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();
}
可意外的是竟然無情的報錯了:
奇葩的是當我再次刷新的時候異常又不一樣了,于是連著刷新好多次,總結出現(xiàn)過的異常有下面這些:
1、 未將對象引用設置到對象的實例。
2、 已添加了具有相同鍵的項。
3、 集合已修改;可能無法執(zhí)行枚舉操作。
4、 一個 EdmType 不能多次映射到 CLR 類。EdmType“SmsModel.MemberCard”映射了一次以上。
其中1和2是出現(xiàn)最多的,而且所有異常都是出現(xiàn)在Add的時候,各種吃瓜表情~沒辦法,接著一一斷點調(diào)試,還是沒找出原因,出于進度考慮,換成了另一種方案,也就是用DbSet的AddRange方法。先在Parallel中累加出一個實體List,然后一次性添加到DbSet中,代碼演變?yōu)?
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();
}
}
然后編譯、測試,沒問題,就先放著了。
分析問題
第二天到公司心里還在糾結這個問題,于是打開頁面輸入生成的數(shù)據(jù)量1000(真實項目中的循環(huán)次數(shù)是手動輸入的),點按鈕提交,嗯,又吃瓜般的異常了…:
心想昨天測試都好好的?。ㄆ鋵嵶蛱燧斎氲氖?0,心虛臉...),沒辦法,上斷點吧,一看嚇一跳:
明明循環(huán)1000次,結果只有971條數(shù)據(jù),而且里面還有為null的,經(jīng)過多次調(diào)試發(fā)現(xiàn)這是一個隨機現(xiàn)象,Count是隨機的null也是隨機的,有時出現(xiàn)有時沒有,初步判斷這是一個在多線程情況下引發(fā)的一個資源調(diào)配異常。So,上MSDN看了一下List的介紹,最后面“線程安全”寫著:
一切貌似都清楚了,于是打算驗證一下結果,加上了鎖,測試結果為:
list里面也沒有再出現(xiàn)null了,確認是因為多線程安全引起的異常。于是想起昨天那個問題是否也是同樣的問題,再上MSDN搜了一下DbContext類和DbSet類,都是這樣說的:
接著就給dbcontext上了鎖,測試,這次總算如我所料,完美運行。但是不解的是最初那幾個異常是如何產(chǎn)生的,List中雖然數(shù)量不夠也存在為null的對象,但是并沒有直接爆出異常?,F(xiàn)在只知道是線程問題,再詳細的也搞不清楚,有知道的大神還麻煩指點一下。
尋找解決方案并驗證結論
也想過用Partitioner分區(qū)來做,但是仔細一想,雖然分區(qū)內(nèi)部是單線程,但是區(qū)與區(qū)之間還是多線程的,如果分的太細也就失去了Parallel的意義,只得另尋出路。還好Framework為我們也提供了一些線程安全的泛型集合(比如ConcurrentBag、ConcurrentQueue等),不過其本質還是用了鎖,于是就綜合做了一下單線程list、多線程list加鎖、多線程ConcurrentBag、多線程ConcurrentQueue的性能對比,結果如下:
循環(huán)1000次時:
循環(huán)10000次時:
循環(huán)100000次時:
得出結論就是,在執(zhí)行次數(shù)超大時用線程安全類型會更慢,在執(zhí)行次數(shù)較少時線程安全類型也沒什么優(yōu)勢。
解決問題
最后在經(jīng)過仔細測試驗證和考慮項目實際需求(幾乎不可能一次10000)后,去繁從簡,回歸原始,用最簡單直白的寫法單線程循環(huán)來完成。雖然一番折騰下來還是回到最初,但是這過程中讓我發(fā)現(xiàn)了意料之外問題,然后找到了原因,然后測試驗證,最終得到了最優(yōu)解決方案。還是那句話,填完坑,你就比之前更強大了!
上一篇:C#的Process類調(diào)用第三方插件實現(xiàn)PDF文件轉SWF文件
欄 目:C#教程
本文標題:在Parallel中使用DbSet.Add()發(fā)現(xiàn)的一系列多線程問題和解決思路詳解
本文地址:http://www.jygsgssxh.com/a1/C_jiaocheng/6177.html
您可能感興趣的文章
- 01-10C#通過反射獲取當前工程中所有窗體并打開的方法
 - 01-10C#實現(xiàn)Winform中打開網(wǎng)頁頁面的方法
 - 01-10C#實現(xiàn)由四周向中心縮小的窗體退出特效
 - 01-10Extjs4如何處理后臺json數(shù)據(jù)中日期和時間
 - 01-10C#實現(xiàn)將窗體固定在顯示器的左上角且不能移動的方法
 - 01-10C#中DataGridView常用操作實例小結
 - 01-10C#編程獲取資源文件中圖片的方法
 - 01-10asp.net中XML如何做增刪改查操作
 - 01-10C#實現(xiàn)在Form里面內(nèi)嵌dos窗體的方法
 - 01-10C#利用反射技術實現(xiàn)去掉按鈕選中時的邊框效果
 


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
 - 01-10關于ASP網(wǎng)頁無法打開的解決方案
 - 01-10WinForm限制窗體不能移到屏幕外的方法
 - 01-10WinForm繪制圓角的方法
 - 01-10C#實現(xiàn)txt定位指定行完整實例
 - 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新
 - 01-10C#停止線程的方法
 - 01-10C#實現(xiàn)清空回收站的方法
 - 01-10C#通過重寫Panel改變邊框顏色與寬度的
 - 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已
 
隨機閱讀
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
 - 01-10delphi制作wav文件的方法
 - 08-05織夢dedecms什么時候用欄目交叉功能?
 - 04-02jquery與jsp,用jquery
 - 01-10使用C語言求解撲克牌的順子及n個骰子
 - 01-11ajax實現(xiàn)頁面的局部加載
 - 01-10SublimeText編譯C開發(fā)環(huán)境設置
 - 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
 - 01-10C#中split用法實例總結
 - 08-05DEDE織夢data目錄下的sessions文件夾有什
 


