2011-08-23 49 views
4

我抓住System.Linq.Dynamic.DynamicQueryable從這裏: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspxSystem.Linq.Dynamic。選擇(「新的......」)似乎沒有線程安全

是我遇到的問題在代碼看起來像這樣:

var results = dataContext.GetTable<MyClass>.Select("new (MyClassID, Name, Description)").Take(5); 

看來,如果該行代碼由多個線程執行的幾乎同時,在他們的ClassFactory.GetDynamicClass()方法,它看起來像這樣微軟的動態Linq的代碼崩潰:

public Type GetDynamicClass(IEnumerable<DynamicProperty> properties) 
    { 
     rwLock.AcquireReaderLock(Timeout.Infinite); 
     try 
     { 
      Signature signature = new Signature(properties); 
      Type type; 
      if (!classes.TryGetValue(signature, out type)) 
      { 
       type = CreateDynamicClass(signature.properties); 
       classes.Add(signature, type); // <-- crashes over here! 
      } 
      return type; 
     } 
     finally 
     { 
      rwLock.ReleaseReaderLock(); 
     } 
    } 

崩潰是一個簡單的字典錯誤:「具有相同密鑰的項目已被添加。」

在Ms代碼中,rwLock變量是一個ReadWriterLock類,但它沒有阻止多個線程進入classes.TryGetValue()if語句,很明顯,Add會失敗。

我可以很容易地在創建兩個或更多線程的代碼中複製這個錯誤,這些線程嘗試執行Select(「new」)語句。

無論如何,我想知道是否有其他人遇到過這個問題,以及是否有修復或可以實施的解決方法。

謝謝。

+0

因爲你有,你可以有效地使用'ConcurrentDictionary'交換'Dictionary'解決這個問題的來源 - 這將是相當快(大多數操作都是無鎖的)並解決線程問題(因爲它是線程安全的) – Yahia

+0

這顯然是一個錯誤。好東西,你可以修復它,在調用Add之前使用UpgradeToWriterLock()。 –

+0

只需升級到寫入鎖定是不夠的,您需要在獲得X鎖定後再次檢查(嘗試獲取)值*。 –

回答

2

我做了以下(需要.NET 4或更高使用System.Collections.Concurrent):

  • 改變classes字段到ConcurrentDictionary<Signature, Type>
  • 除去所有ReaderWriterLock rwLock字段和所有代碼引用它,
  • 更新GetDynamicClass到:

    public Type GetDynamicClass(IEnumerable<DynamicProperty> properties) { 
        var signature = new Signature(properties); 
        return classes.GetOrAdd(signature, sig => CreateDynamicClass(sig.properties)); 
    } 
    
  • 取出classCount領域和更新CreateDynamicClass使用classes.Count代替:

    Type CreateDynamicClass(DynamicProperty[] properties) { 
        string typeName = "DynamicClass" + Guid.NewGuid().ToString("N"); 
    ... 
    
+1

這裏還有一個潛在的競爭條件,因爲你讀了classes.Count沒有鎖。你可能會得到兩個使用相同數字後綴創建的類。更安全的方法是使用「DynamicClass」+ Guid.NewGuid()。 –

+0

乾杯@SamuelJack,很好! –