2010-05-19 90 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已有一段时间的候选人,因此被重新编码了更多可扩展的数据结构。