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

Dea*_*one 2 c# parallel-processing datatable parallel.foreach

我正在转换现有应用程序以利用多个处理器。我有一些嵌套循环,并且我已将最内层循环转换为 Parallel.Foreach 循环。在原始应用程序中,在最内层循环内,代码将调用DataTable.NewRow()实例化适当布局的新 DataRow、填充列并将填充的 DataRow 添加到带有 的 DataTable 中DataTable.Add()。但由于 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

   at System.Data.DataTable.NewRow(Int32 record)
   at System.Data.DataTable.NewRow()
   at ...
Run Code Online (Sandbox Code Playgroud)

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

感谢您的任何帮助。

Sco*_*nen 6

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

NewRow您可以将值放入数组或列表中,而不是使用和填充行object。然后,当您收集了所有数据后,您可以将其全部添加到DataTable.

var newRow = table.NewRow();
newRow.ItemArray = values; // array of values
table.Rows.Add(newRow);
Run Code Online (Sandbox Code Playgroud)

这样,您就可以并行创建数据,而不会在将数据添加到DataTable.


查看代码DataTable

它包含多个字段:

private readonly DataRowBuilder rowBuilder;
internal readonly RecordManager recordManager;
Run Code Online (Sandbox Code Playgroud)

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;
    }
Run Code Online (Sandbox Code Playgroud)

......还有更多我没有遵循的。但显而易见的是,它NewRow()不仅仅返回一个新行,它还修改了DataTable整个实例的状态。

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

另一个标志位于文档中NewRow

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

它没有说明如果您调用Clear()而不添加或删除使用 创建的行会发生什么NewRow()。例外吗?我会死吗?所以我尝试了。我还在这里,但调用Clear()将每行中的所有值替换为DBNull.Value,进一步强调了这些行在被添加到DataTable. 他们是国家的一部分。