Parallel.ForEach и DataTable - не является ли DataTable.NewRow () поточно-безопасной операцией «чтения»? - PullRequest
0 голосов
/ 20 мая 2019

Я конвертирую существующее приложение, чтобы использовать преимущества нескольких процессоров. У меня есть несколько вложенных циклов, и я преобразовал самый внутренний цикл в цикл 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);
}

Поэтому, когда это запускается, я вижу: «Индекс находился за пределами массива». исключение при звонке на MyDataTable.NewRow(). Но не будет ли NewRow () поточно-ориентированной операцией чтения? Конечно, он создает новый объект DataRow, и это не чтение. Но ему не нужно изменять объект DataTable, не так ли?

Это может немного помочь ... Когда я смотрю на исключение, два верхних элемента в моем стеке вызовов ...

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

И я вижу, что NewRow() вызывает то, что должно быть закрытым NewRow(int32) методом. Так что, возможно, в этом проблема. Но я не уверен, как решить это. Если мне нужно, я могу получить создание и вместо создания экземпляра объекта DataRow из моего цикла Parallel.Foreach, просто создать пользовательский объект, который очень похож на мой DataTable, и после завершения цикла создать экземпляр фактического DataRow и добавить их в DataTable. Но это менее чем элегантно, и создает "ненужные" объекты. И моя цель - улучшить производительность, так что это кажется контрпродуктивным.

Спасибо за любую помощь.

1 Ответ

2 голосов
/ 20 мая 2019

Нет, NewRow не является операцией чтения и не является поточно-ориентированной.

Вместо использования NewRow и заполнения строки вы можете просто поместить свои значения в массив или список object. Затем, когда вы собрали все свои данные, вы можете добавить их в DataTable.

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

Таким образом, вы можете распараллеливать создание ваших данных, не сталкиваясь с проблемами при добавлении их в DataTable.


Глядя на исходный код для 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. Но я был бы неправ, и это определенно не безопасно для потоков.

Еще один флаг в документации для NewRow

После создания DataRow вы можете добавить его в DataRowCollection с помощью свойства Rows объекта DataTable. Когда вы используете NewRow для создания новых строк, строки должны быть добавлены или удалены из таблицы данных перед вызовом Clear.

В нем не сказано, что произойдет, если вы вызовете Clear() без добавления или удаления строки, созданной с помощью NewRow(). Исключение? Я умру? Итак, я попробовал. Я все еще здесь, но вызов Clear() заменил все значения в каждой строке на DBNull.Value, еще раз подчеркнув, что строки не "освобождены" до тех пор, пока они не будут добавлены в DataTable. Они являются частью его государства.

...