2010-05-19 94 views
0

介紹ADO.NET的DataTable/DataRow的線程安全

今天上午,他在和結果不一致(即,列值有時會出來空時,他們不應該是)一個問題向我報告的用戶我們提供的一些並行執行代碼作爲內部框架的一部分。這段代碼在過去一直很好,並沒有被篡改最近,但它讓我開始思考下面的代碼片段:

代碼示例

lock (ResultTable) 
{ 
    newRow = ResultTable.NewRow(); 
} 

newRow["Key"] = currentKey; 
foreach (KeyValuePair<string, object> output in outputs) 
{ 
    object resultValue = output.Value; 
    newRow[output.Name] = resultValue != null ? resultValue : DBNull.Value; 
} 

lock (ResultTable) 
{ 
    ResultTable.Rows.Add(newRow); 
} 

(無保證,編譯,手-edited掩蓋proprietery信息。)

說明

我們在歐鎖定代碼等地的這種級聯型r系統,並且工作正常,但這是我遇到的與ADO .NET交互的第一個級聯鎖定代碼。衆所周知,框架對象的成員通常不是線程安全的(這種情況就是這種情況),但級聯鎖定應確保我們不會同時讀取和寫入ResultTable.Rows。我們很安全,對吧?

假設

好,級聯鎖碼不保證我們不會從讀取或在同一時間,我們在新的分配值列寫ResultTable.Rows 行。如果ADO .NET使用某種緩衝區來分配不是線程安全的列值,即使涉及不同的對象類型(DataTable與DataRow),也會如此?

有沒有人遇到過這樣的事情?我想我會問在這裏StackOverflow的打我的頭這小時結束:)

結論

好之前,共識似乎是改變級聯鎖全鎖已經解決了問題。這不是我預期的結果,但完整的鎖定版本在經過很多很多很多測試之後並未產生問題。

教訓:請謹慎使用您不能控制的API上使用的級聯鎖。誰知道可能會發生什麼?

+1

艾倫......我可以使用更多的信息。你是否以斷開的方式使用DataSet對象?數據存儲是什麼以及如何安排更新? ADO.NET是一個很多頭獸:) – Rusty 2010-05-19 20:37:34

+0

很好的問題!是的,這是一個以斷開方式使用的DataTable。在這種情況下沒有數據存儲(全部在內存中)。線程通過我們遍佈各地使用的內部線程池的實例進行調度。在這種特殊情況下,線程池通過信號量在8個線程中被限制。 無論如何,我希望能夠充分回答你的問題! – 2010-05-19 20:52:05

+0

最優秀。我現在正在處理一些斷開連接的數據集......我會嘗試進行一些測試。 您正在運行3.5或4.0嗎? – Rusty 2010-05-19 22:28:55

回答

2

阿倫,

我無法找到你的方法的任何具體問題,不是我的測試是詳盡。這裏有一些想法,我們堅持(我們所有的應用程序都螺紋中心):

在可能的情況:

[1]讓所有的數據訪問完全原子。由於多線程應用程序中的數據共享是各種無法預料的線程交互的絕佳場所。

[2]避免鎖定類型。如果類型不知道是線程安全的,則寫一個包裝器。

[3]包含允許快速識別正在訪問共享資源的線程的結構。如果系統性能允許,請將此信息記錄在調試級別之上並低於常規操作日誌級別。

[4]任何代碼,包括System。* et.al,未在內部明確記錄爲線程安全測試,不是線程安全的。傳聞和其他人的言語不計算在內。測試它並寫下來。

希望這有一定的價值。

+0

嘿,生鏽!感謝您一直在研究這個問題!並感謝提示! – 2010-05-21 13:14:54

+0

@Allen:當你瞭解更多,請發佈:) – Rusty 2010-05-21 16:14:02

+0

添加了上述原始帖子的結論。 – 2010-05-27 18:07:44

2

我讀過一篇文章,表示他們發現內部結構在DataTable中使用了一個通用行來插入操作。創建新記錄的多個線程將覆蓋常見行上的數據,並相互侵蝕導致問題。解決方法是在添加行時鎖定表,以便一次只能有一個線程添加新行。

1

您的代碼對我來說看起來很好,但我建議您在添加新創建的行之前使用ResultTable.Rows.SyncRoot進行鎖定,以便剩下的ResultTable對象可以被其他進程自由訪問。

lock (ResultTable.Rows.SyncRoot) 
1

.NET的該位可能在過去七年(!)已經發生了變化,但是,要回答這個問題,列值的緩衝的假設是不正確的的.NET 4.7.1。從the source in corefx/DataRow.cs的角度來看,問題是_tempRecord字段周圍的競態條件,該字段存儲行在數據表中的位置。觸發BeginEditInternal()的任何寫入都可能會修改此字段,其中包括值更新。當兩次寫入衝突時,可能會遇到由另一個設置的_tempRecord的值,因此會更新與預期不同的行。這與Microsoft's documentation一致,聲明任何寫入必須同步(強調增加)。託尼早些時候的回答描述了這種行爲的一個子集。

作爲一個例子,我最近通過性能改進破壞了上面代碼示例中顯示的鎖定方法後的代碼。代碼穩定並且在1.5年內沒有問題地運行,但是,在每秒超過2000個新行的某個位置上,至少有幾萬個寫入中的一個始終以錯誤的行結束。

一個可能的修復方法是鎖定寫入,但將它們分組以限制性能影響,方法是將鎖的數量減到最少。另一種方法是給每個線程自己的表更新並稍後合併結果。就我而言,性能關鍵部分一直是DataTable已有一段時間的候選人,因此被重新編碼了更多可擴展的數據結構。