在Parallel中使用DbSet.Add()發現的一系列多線程問題和解決過程

来源:http://www.cnblogs.com/hohoa/archive/2016/11/13/6060228.html
-Advertisement-
Play Games

發現問題 需求很簡單,大致就是要批量往資料庫寫數據,於是打算用Parallel並行的方式寫入,希望能利用電腦多核特性加快程式執行速度。想的很美好,於是快速擼了類似下麵的一串代碼: 可意外的是竟然無情的報錯了: 奇葩的是當我再次刷新的時候異常又不一樣了,於是連著刷新好多次,總結出現過的異常有下麵這些 ...


發現問題

需求很簡單,大致就是要批量往資料庫寫數據,於是打算用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)後,去繁從簡,回歸原始,用最簡單直白的寫法單線程迴圈來完成。雖然一番折騰下來還是回到最初,但是這過程中讓我發現了意料之外問題,然後找到了原因,然後測試驗證,最終得到了最優解決方案。還是那句話,填完坑,你就比之前更強大了!



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ReSharper 10.0.0.1 Ultimate 完美破解補丁使用方法,本資源來自互聯網,感謝吾樂吧軟體站的分享。 ReSharper是一款由jetbrains開發的針對C#, VB.NET, ASP.NET, XML, 和 XAML的編輯器。沿襲了jetbrains開發工具一貫的優良傳統,R ...
  • 添加引用時生成”勾選允許生成非同步操作” Wcf非同步調用三種方式: 第一種:直接調用非同步方法 var serviceClient = new MyServiceClient(); serviceClient.MessageAsync(); erviceClient.Close(); 第二種:Begin ...
  • 一、MessageBox的Buttons MessageBox.Show可以出現有按鈕的對話框 例如: DialogResult dr = MessageBox.Show("是否要繼續嗎?", "警告!!!", MessageBoxButtons.OKCancel);//它彈出的對話框如下圖所示if ...
  • 一、客戶端設計思路 1.理順設計思路,架構框架 2.設計界面 3.編寫後臺代碼 4.資料庫訪問 二、公共控制項 1、Button(按鈕): ⑴ Enabled :確定是否啟用控制項 ⑵ Visible:確定控制項是否課件; 2、CheckBox(多選項) 、CheckListBox -(多選項列表) 3、 ...
  • Asp.Net MVC4 BundleConfig文件合併、壓縮,網站優化加速 Asp.Net MVC4 BundleConfig文件合併、壓縮,網站優化加速 瀏覽器在向伺服器發送請求的時候,請求的文件鏈接數量是有限制的,如果頁面文件少就沒有什麼問題了,如果文件太多就會導致鏈接失敗等等問題。針對這個 ...
  • C# 知識回顧 - 事件入門 【博主】反骨仔 【原文】http://www.cnblogs.com/liqingwen/p/6057301.html 序 之前通過《C# 知識回顧 - 委托 delegate》、《C# 知識回顧 - 委托 delegate (續)》介紹了委托的基本知識,這次我們來看看 ...
  • ASP.NET Core管道雖然在結構組成上顯得非常簡單,但是在具體實現上卻涉及到太多的對象,所以我們在 “通過重建Hosting系統理解HTTP請求在ASP.NET Core管道中的處理流程”(上篇、中篇、下篇) 中圍繞著一個經過極度簡化的模擬管道講述了真實管道構建的方式以及處理HTTP請求的流程... ...
  • 問題 我們想快速啟動一個 ASP.NET Web API 解決方案。 解決方案 APS.NET 模板一開始就支持 ASP.NET Web API。使用模板往我們的項目中添加 Controller,在我們解決方案的 Controllers 文件夾上右鍵,選擇“添加”->"Scaffolding"。 即 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...