title: ADO.NET DataTable/DataRow Thread Safety
body: <p><strong>Introduction</strong></p>
  
  <p>A user reported to me this morning that he was having an issue with inconsistent results (namely, column values sometimes coming out null when they should not be) of some parallel execution code that we provide as part of an internal framework.  This code has worked fine in the past and has not been tampered with lately, but it got me to thinking about the following snippet:</p>
  
  <p><strong>Code Sample</strong></p>
  
  <pre><code>lock (ResultTable)
  {
      newRow = ResultTable.NewRow();
  }
  
  newRow["Key"] = currentKey;
  foreach (KeyValuePair&lt;string, object&gt; output in outputs)
  {
      object resultValue = output.Value;
      newRow[output.Name] = resultValue != null ? resultValue : DBNull.Value;
  }
  
  lock (ResultTable)
  {
      ResultTable.Rows.Add(newRow);
  }
  </code></pre>
  
  <p>(No guarantees that that compiles, hand-edited to mask proprietery information.)</p>
  
  <p><strong>Explanation</strong></p>
  
  <p>We have this cascading type of locking code other places in our system, and it works fine, but this is the first instance of cascading locking code that I have come across that interacts with ADO .NET.  As we all know, members of framework objects are usually not thread safe (which is the case in this situation), but the cascading locking should ensure that we are not reading and writing to ResultTable.Rows concurrently.  We are safe, right?</p>
  
  <p><strong>Hypothesis</strong></p>
  
  <p>Well, the cascading lock code does not ensure that we are not reading from or writing to ResultTable.Rows <strong>at the same time</strong> that we are <strong>assigning</strong> values to columns in the new row.  What if ADO .NET uses some kind of buffer for assigning column values that is not thread safe--even when different object types are involved (DataTable vs. DataRow)?</p>
  
  <p>Has anyone run into anything like this before?  I thought I would ask here at StackOverflow before beating my head against this for hours on end :)</p>
  
  <p><strong>Conclusion</strong></p>
  
  <p>Well, the consensus appears to be that changing the cascading lock to a full lock has resolved the issue.  That is not the result that I expected, but the full lock version has not produced the issue after many, many, many tests.</p>
  
  <p>The lesson: be wary of cascading locks used on APIs that you do not control.  Who knows what may be going on under the covers!</p>