2015-06-29 30 views
2

執行以下代碼時。使用異步和DataRow的非線程安全代碼

using System; 
using System.Collections.Generic; 
using System.Data; 
using System.Threading.Tasks; 

namespace AsyncDataRow 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      var program = new Program(); 

      try 
      { 
       Console.WriteLine("Execute"); 

       for (int i = 0; i < 100; i++) 
       { 
        program.Execute(); 
       } 
      } 
      catch (AggregateException aggregateException) 
      { 
       foreach (var exception in aggregateException.InnerExceptions) 
       { 
        Console.WriteLine("AggregateException.InnerExceptions: " + exception.Message); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Exception.Message: " + ex.Message); 
      } 

      Console.ReadKey(); 
     } 

     private void Execute() 
     { 
      DataTable dataTable = new DataTable(); 
      dataTable.Columns.Add("ID", typeof(int)); 
      dataTable.Columns.Add("Name", typeof(string)); 

      for (int i = 0; i < 1000; i++) 
      { 
       dataTable.Rows.Add(i, i.ToString()); 
      } 

      var taskList = new List<Task>(); 

      foreach (DataRow dataRow in dataTable.Rows) 
      { 
       taskList.Add(ChangeStringValue(dataRow)); 
      } 

      Task.WaitAll(taskList.ToArray()); 
     } 

     private async Task ChangeStringValue(DataRow dataRow) 
     { 
      var id = (int)dataRow["ID"]; 

      var newValue = await Task.Run(() => CreateNewValue(id)); 

      dataRow["Name"] = newValue; 
     } 

     private string CreateNewValue(int id) 
     { 
      Console.WriteLine(string.Format("{0} - CreateNewValue", id)); 
      return id.ToString() + "New StringValue"; 
     } 
    } 
} 

間歇地System.ArgumentOutOfRangeException被扔在這行代碼

dataRow["Name"] = newValue

異常消息:

Exception thrown: 'System.ArgumentOutOfRangeException' in mscorlib.dll 
Additional information: Index was out of range. Must be non-negative and less than the size of the collection. 

我想更具體的識別問題,但最近我得到了簡化代碼來重現錯誤。

我認爲它與傳入異步方法的DataRow引用類型有關,但我想知道爲什麼這個代碼不是線程安全的。

+1

正如您似乎已經發現的那樣,'DataRow'元素不能同時從多個線程發生變異。你不需要從多個線程操縱它們。 – Servy

回答

1

.Net框架中的線程安全類的數量非常小,並且每個都會明確地調用它的安全性(大部分可在System.Collecions.Concurrent命名空間中找到)。對於所有其他類型,您需要提供您自己的線程安全機制。與數據庫相關的類絕對屬於寫非線程安全類。通常類明確允許多個線程安全讀取。

你的情況DataRow可以讀取從多個線程,你只需要同步寫:

線程安全

這種類型是安全的多線程讀取操作。您必須同步任何寫入操作。

構建大量項目的標準方法 - 在範圍中拆分項目並獨立生成,然後合併完畢。

基於原始代碼的部分樣本,MyRowResult是保存需要更新的行的所有數據的類。

 var taskList = new List<Task<MyDataRow>(); 

     foreach (DataRow dataRow in dataTable.Rows) 
     { 
      taskList.Add(ChangeStringValue(dataRow)); 
     } 

     // await for proper async code, WaitAll is fin for console app. 
     await Task.WhenAll(taskList.ToArray()); 

     // back to single thread - safe to update rows 
     foreach (var task in taskList) 
     { 
      task.Result.DataRow["Name"] = task.Result.Name; 
     } 
    } 

    private async Task<MyRowResult> ChangeStringValue(DataRow dataRow) 
    { 
     // on multiple threads - perform read only operations of rows 
     // which is safe as explicitly called out in the documentation 
     // "This type is safe for multithreaded read operations." 
     var id = (int)dataRow["ID"]; 

     var newValue = await Task.Run(() => CreateNewValue(id)); 

     return new MyRowResult { Name = newValue, DataRow = dataRow }; 
    } 

注:

  • 一些類(比如UI控件)需要更嚴格的線程安全 - 可以在原始線程調用僅
  • 助手在Parallel類可以簡化並行運行代碼(如Parallel.ForEach)。