Parallel.ForEach和DataTable-DataTable.NewRow()不是线程安全的“读取”操作吗?

时间:2019-05-20 15:57:58

标签: c# datatable parallel-processing parallel.foreach

我正在转换现有应用程序以利用多个处理器。我有一些嵌套循环,并且已将最里面的循环转换为Parallel.Foreach循环。在原始应用程序的最内层循环中,代码将调用DataTable.NewRow()以实例化具有适当布局的新DataRow,填充列,然后使用DataTable.Add()将填充的DataRow添加到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()调用了必须是私有NewRow(int32)方法。所以也许就是这个问题。但我不确定如何解决。如果需要的话,我可以创建它,而不必从我的Parallel.Foreach循环中实例化DataRow对象,只需实例化一个看起来很像我的DataTable的自定义对象,一旦循环退出,就实例化实际的DataRows并将其添加到数据表。但这不算优雅,而是实例化“不必要的”对象。而我的目标是提高性能,从而适得其反。

感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

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

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

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

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


查看DataTable的{​​{3}}:

它包含多个字段:

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。但是我会错的,而且绝对不是线程安全的。

source code

中还有一个标志
  

创建DataRow后,可以通过DataTable对象的Rows属性将其添加到DataRowCollection。使用NewRow创建新行时,必须在调用Clear之前将这些行添加到数据表中或从数据表中删除。

如果您在不添加或删除用Clear()创建的行的情况下调用NewRow(),并没有说什么。有例外吗我会死吗?所以我尝试了。我仍然在这里,但是调用Clear()会将每行中的所有值替换为DBNull.Value,进一步强调了只有在将行添加到DataTable之前,这些行才是“无形的”。它们是其状态的一部分。