2011-08-23 53 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,很好! –