You are threading and need to use Monitor.Enter when updating DataRows on a table. Did you know that DataTables are not thread safe? Even if your threads are updating different rows of the same DataTable, you can get concurrency errors that manifest themselves in weird ways.
Here’s the error I was getting:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Parameter name: index
Solution:
Use Monitor.Enter to protect updates to your DataRow.
public void DoWorkUpdatingRow(object state)
{
List<DataRow> rowsToWorkOn = (List<DataRow>)state;
foreach (DataRow dr in rowsToWorkOn)
{
Monitor.Enter(this);
try
{
dr["value"] = dr["id"] + " new value";
}
finally
{
Monitor.Exit(this);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
// Setup of data for worker threads
DataTable dt = new DataTable();
dt.Columns.Add("id", typeof(int));
dt.Columns.Add("value", typeof(string));
for (int i = 0; i < 5000; i++)
{
DataRow newRow = dt.NewRow();
newRow["id"] = i;
newRow["value"] = i + " text";
dt.Rows.Add(newRow);
}
// Chunk out the work
const int numberOfThreads = 4;
int chunkSize = dt.Rows.Count / numberOfThreads;
int currentRow = 0;
for (int i = 0; i < numberOfThreads; i++)
{
List<DataRow> rowsToWorkOn = new List<DataRow>();
int maxRowNumber = (currentRow + chunkSize);
if (i == numberOfThreads)
{
// Last chunk, don't run over the end of the datatable
maxRowNumber = dt.Rows.Count;
}
for (int j = currentRow; j < maxRowNumber; j++)
{
rowsToWorkOn.Add(dt.Rows[j]);
}
currentRow += chunkSize;
ThreadPool.QueueUserWorkItem(DoWorkUpdatingRow, rowsToWorkOn);
}
}
Thanks. It was exactly the solution I was looking for. BUT, unfortunately, it eliminated all the performance gains I'd expected.
ReplyDelete