ADO.NET DataTable / DataRow线程安全

时间:2010-05-19 20:22:02

标签: c# ado.net

简介

用户今天早上向我报告说,他遇到了一些问题,即我们作为内部框架的一部分提供的某些并行执行代码的结果不一致(即,列值有时会在它们不应该出现时为null)。这段代码过去运行良好,最近没有被篡改过,但它让我想到了以下片段:

代码示例

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);
}

(不保证编辑,手工编辑以掩盖所有权信息。)

解释

我们在系统中的其他位置都有这种级联类型的锁定代码,它工作正常,但这是我遇到的与ADO .NET交互的级联锁定代码的第一个实例。众所周知,框架对象的成员通常不是线程安全的(在这种情况下就是这种情况),但是级联锁定应该确保我们不会同时读取和写入ResultTable.Rows。我们很安全,对吧?

假设

好吧,级联锁定代码不能确保我们不会读取或写入ResultTable.Rows 同时我们分配值新行。如果ADO .NET使用某种缓冲区来分配非线程安全的列值,即使涉及不同的对象类型(DataTable与DataRow),该怎么办?

有没有人遇到过这样的事情?我以为我会在StackOverflow上问这个问题,然后在几个小时内对抗这个问题:)

结论

嗯,共识似乎是将级联锁更改为完全锁已解决了这个问题。这不是我预期的结果,但完全锁定版本在许多很多测试后都没有产生问题。

教训:警惕您无法控制的API上使用的级联锁。谁知道封面下会发生什么呢!

4 个答案:

答案 0 :(得分:2)

阿伦,

我找不到您的方法的任何具体问题,而不是我的测试是详尽无遗的。以下是我们坚持的一些想法(我们所有的应用程序都是以线程为中心的):

尽可能:

[1]使所有数据访问完全原子化。由于多线程应用程序中的数据共享是各种无法预料的线程交互的绝佳场所。

[2]避免锁定类型。如果类型不知道是线程安全的,请写一个包装器。

[3]包含允许快速识别正在访问共享资源的线程的结构。如果系统性能允许,请将此信息记录在调试级别之上,并低于通常的操作日志级别。

[4]任何代码,包括System。* et.al,内部未明确记录为线程安全测试不是线程安全。传闻和其他人的口头表达并不重要。测试并写下来。

希望这有一些价值。

答案 1 :(得分:2)

我曾经读过一篇文章说他们发现内部在DataTable中使用公共行进行插入操作。创建新记录的多个线程将覆盖公共行上的数据并相互腐蚀导致问题。修复是在添加行时锁定表,因此一次只有一个线程可以添加新行。

答案 2 :(得分:1)

您的代码对我来说也很好,但我建议您在添加新创建的行之前使用ResultTable.Rows.SyncRoot进行锁定,以便其他ResultTable对象可以被其他人自由访问过程

lock (ResultTable.Rows.SyncRoot)

答案 3 :(得分:1)

.NET的这一点可能在过去七(!)年内有所改变,但为了回答这个问题,从.NET 4.7.1开始,列值缓冲的假设是不正确的。从the source in corefx/DataRow.cs看,问题是_tempRecord字段周围的竞争条件,该字段存储行在数据表中的位置。任何触发对BeginEditInternal()的调用的写入都可能修改此字段,其中包括值更新。当两个写入冲突时,最终可能会跟随另一个写入的_tempRecord值,因此会更新与预期不同的行。这与Microsoft's documentation一致,表明必须同步任何写入(强调添加)。托尼先前的回答描述了这种行为的一个子集。

作为一个例子,我最近通过改进性能改进了上面代码示例中显示的锁定方法后的代码。代码是稳定的并且在没有问题的情况下运行了1。5年,但是,在每秒2000行以上的新行中,至少有几万次写入中的一行始终在错误的行上运行。

一种可能的解决方法是锁定每次写入,但将它们分组以通过最小化锁定来限制性能影响。另一种方法是给每个线程自己的表进行更新,然后合并结果。就我而言,性能关键部分已成为离开DataTable一段时间的候选者,因此重新编码了更具可伸缩性的数据结构。