Parallel.ForEach 和 DataTable - DataTable.NewRow() 不是线程安全的 "read" 操作吗?
Parallel.ForEach and DataTable - Isn't DataTable.NewRow() a thread safe "read" operation?
我正在转换现有应用程序以利用多个处理器。我有一些嵌套循环,我已将最内层循环转换为 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()
时看到 "Index was outside the bounds of the array." 异常。但是 NewRow() 不是线程安全的读取操作吗?当然,它实例化了一个新的 DataRow 对象,这不是读取。但是它不需要修改DataTable对象,是吗?
这可能有点帮助...当我查看异常时,我的调用堆栈中的前两项是...
at System.Data.DataTable.NewRow(Int32 record)
at System.Data.DataTable.NewRow()
at ...
而且我看到 NewRow()
调用了必须是私有的 NewRow(int32)
方法。所以也许这就是问题所在。但我不确定如何解决它。如果必须,我可以开始创建,而不是从我的 Parallel.Foreach 循环中实例化 DataRow 对象,只需实例化一个看起来很像我的 DataTable 的自定义对象,一旦循环退出,实例化实际的 DataRows 并添加他们到数据表。但这不够优雅,并且会实例化 "unnecessary" 个对象。而我的目标是提高性能,所以这似乎适得其反。
感谢您的帮助。
不,NewRow
不是 "read" 操作并且不是线程安全的。
您可以不使用 NewRow
并填充行,而是将您的值放在 object
的数组或列表中。然后,当您收集完所有数据后,您可以将其全部添加到 DataTable
.
var newRow = table.NewRow();
newRow.ItemArray = values; // array of values
table.Rows.Add(newRow);
这样,当您将数据添加到 DataTable
.
时,您可以并行创建数据而不会 运行 出现问题
正在查看 DataTable
的 source code:
它包含各种字段:
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()
不仅仅是 return 一个新行 - 它修改了整个地方的 DataTable
实例的状态。
文档从来没有说它是线程安全的,但我猜是因为你仍然必须将行添加到 table,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()
创建的行会发生什么。例外?我会死吗?所以我试过了。我还在这里,但是调用 Clear()
将每一行中的所有值替换为 DBNull.Value
,进一步强调这些行不是 "disembodied" 直到它们被添加到 DataTable
.它们是其状态的一部分。
我正在转换现有应用程序以利用多个处理器。我有一些嵌套循环,我已将最内层循环转换为 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()
时看到 "Index was outside the bounds of the array." 异常。但是 NewRow() 不是线程安全的读取操作吗?当然,它实例化了一个新的 DataRow 对象,这不是读取。但是它不需要修改DataTable对象,是吗?
这可能有点帮助...当我查看异常时,我的调用堆栈中的前两项是...
at System.Data.DataTable.NewRow(Int32 record)
at System.Data.DataTable.NewRow()
at ...
而且我看到 NewRow()
调用了必须是私有的 NewRow(int32)
方法。所以也许这就是问题所在。但我不确定如何解决它。如果必须,我可以开始创建,而不是从我的 Parallel.Foreach 循环中实例化 DataRow 对象,只需实例化一个看起来很像我的 DataTable 的自定义对象,一旦循环退出,实例化实际的 DataRows 并添加他们到数据表。但这不够优雅,并且会实例化 "unnecessary" 个对象。而我的目标是提高性能,所以这似乎适得其反。
感谢您的帮助。
不,NewRow
不是 "read" 操作并且不是线程安全的。
您可以不使用 NewRow
并填充行,而是将您的值放在 object
的数组或列表中。然后,当您收集完所有数据后,您可以将其全部添加到 DataTable
.
var newRow = table.NewRow();
newRow.ItemArray = values; // array of values
table.Rows.Add(newRow);
这样,当您将数据添加到 DataTable
.
正在查看 DataTable
的 source code:
它包含各种字段:
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()
不仅仅是 return 一个新行 - 它修改了整个地方的 DataTable
实例的状态。
文档从来没有说它是线程安全的,但我猜是因为你仍然必须将行添加到 table,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()
创建的行会发生什么。例外?我会死吗?所以我试过了。我还在这里,但是调用 Clear()
将每一行中的所有值替换为 DBNull.Value
,进一步强调这些行不是 "disembodied" 直到它们被添加到 DataTable
.它们是其状态的一部分。