c# - Parallel.ForEach 和 DataTable - DataTable.NewRow() 不是线程安全的 "read"操作吗?

标签 c# datatable parallel-processing parallel.foreach

我正在转换现有应用程序以利用多个处理器。我有一些嵌套循环,并且我已将最内层循环转换为 Parallel.Foreach 循环。在原始应用程序中,在最内层循环中,代码将调用 DataTable.NewRow()要实例化适当布局的新 DataRow,填充列并将填充的 DataRow 添加到带有 DataTable.Add() 的 DataTable 中.但由于 DataTable 仅对读取操作是线程安全的,因此我将处理过程转换为将填充的 DataRow 对象添加到 ConcurrentBag<DataRow> 中。目的。然后,一旦 Parallel.Foreach 循环完成,我将迭代 ConcurrentBag 并将 DataRow 对象添加到 DataTable 中。看起来像这样……

DataTable MyDataTable = new DataTable()
// Add columns to the data table

For(int OuterLoop = 1; OuterLoop < MaxValue; OuterLoop++)
{
    //Do Stuff...

    ConcurrentBag<DataRow> CB = new ConcurrentBag<DataRow>();

    Parallel.Foreach(MyCollectionToEnumerate, x => 
    {
        //Do Stuff

        DataRow dr = MyDataTable.NewRow();
        // Populate dr...
        CB.Add(dr);
    {);

    ForEach(DataRow d in CB)
        MyDataTable.Add(d);
}

所以当它运行时,我看到“索引超出了数组的范围。”调用 MyDataTable.NewRow() 时出现异常.但是 NewRow() 不是线程安全的读取操作吗?当然,它实例化了一个新的 DataRow 对象,这不是读取。但它不需要修改 DataTable 对象,不是吗?

这可能有点帮助...当我查看异常时,我的调用堆栈中的前两项是...

   at System.Data.DataTable.NewRow(Int32 record)
   at System.Data.DataTable.NewRow()
   at ...

我看到了 NewRow()调用必须是私有(private)的 NewRow(int32)方法。所以也许这就是问题所在。但我不确定如何解决它。如果必须的话,我可以开始创建,而不是从我的 Parallel.Foreach 循环中实例化 DataRow 对象,只需实例化一个看起来很像我的 DataTable 的自定义对象,一旦循环退出,实例化实际的 DataRows 并将它们添加到数据表。但这不够优雅,并且会实例化“不必要的”对象。我的目标是提高性能,所以这似乎适得其反。

感谢您的帮助。

最佳答案

不,NewRow 不是“读”操作,也不是线程安全的。

除了使用 NewRow 并填充行之外,您还可以将您的值放在数组或 object 列表中。然后,当您收集完所有数据后,您可以将其全部添加到 DataTable

var newRow = table.NewRow();
newRow.ItemArray = values; // array of values
table.Rows.Add(newRow);

这样,当您将数据添加到 DataTable 时,您可以并行创建数据而不会遇到问题。


查看 source code对于 DataTable:

它包含各种字段:

private readonly DataRowBuilder rowBuilder;
internal readonly RecordManager recordManager;

NewRow() 调用 NewRow(-1)NewRow(int) 修改这些字段的状态:

    internal DataRow NewRow(int record) {
        if (-1 == record) {
            record = NewRecord(-1);
        }

        rowBuilder._record = record;                  // here
        DataRow row = NewRowFromBuilder( rowBuilder );
        recordManager[record] = row;                  // here

        if (dataSet != null)
            DataSet.OnDataRowCreated( row );

        return row;
    }

...还有很多我没有跟进。但显而易见的是,NewRow() 不仅仅返回一个新行 - 它修改整个地方的 DataTable 实例的状态。

文档从来没有说它是线程安全的,但我猜是因为你仍然需要将行添加到表中,所以 NewRow 没有修改 DataTable。但我错了,它绝对不是线程安全的。

另一个标志在 documentation for NewRow

After creating a DataRow, you can add it to the DataRowCollection, through the DataTable object's Rows property. When you use NewRow to create new rows, the rows must be added to or deleted from the data table before you call Clear.

它没有说明如果调用 Clear() 而不添加或删除使用 NewRow() 创建的行会发生什么。异常(exception)?我会死吗?所以我试过了。我还在这里,但是调用 Clear() 将每行中的所有值替换为 DBNull.Value,进一步强调这些行在它们出现之前不会“脱离实体”被添加到 DataTable。它们是其状态的一部分。

关于c# - Parallel.ForEach 和 DataTable - DataTable.NewRow() 不是线程安全的 "read"操作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56224441/

相关文章:

c# - 如何通过没有 IP 地址的网卡发送 WOL 包(或任何东西)?

javascript - 数据表固定列(仅限右侧)问题

c# - 我们可以在c#中的datatable.select中添加参数吗

java - 打印在网络论坛上发布大量问题和大量答案的前 10 位用户

c# - 属性引起的 Stackoverflow 异常

c# - 如何使用 C# 从 pop.gmail.com 获取电子邮件?

c# - 使用 c#/htmlagilitpack 无法从 amazon.com 获取正确的信息

javascript - 数据表中的 masterCheckbox

c# - C# 中的并行处理/并行编程

c++ - 使用 MPI 在处理器之间分配工作,但所有处理器都在完成整个工作,而不是只做其中的一部分