2014-01-10 36 views
5

获取集合已修改;枚举操作可能不会执行。例外获取收藏已修改;枚举操作可能不会执行。异常

代码:

public static string GetValue(List<StateBag> stateBagList, string name) 
{ 
    string retValue = string.Empty; 

    if (stateBagList != null) 
    { 
     foreach (StateBag stateBag in stateBagList) 
     { 
      if (stateBag.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) 
      { 
       retValue = stateBag.Value; 
      } 
     } 
    } 

    return retValue; 
} 

得到这个例外有一段时间没有时间每次都在这个地方。

堆栈跟踪:

在System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource 资源)

在System.Collections.Generic.List`1.Enumerator.MoveNextRare()

的系统。 Collections.Generic.List`1.Enumerator.MoveNext()

at Tavisca.TravelNxt.Shared.Entities.StateBag.GetValue(List`1 stateBagList,String nam E)


@no一个我已经尝试了下面的代码,但仍然得到例外

代码:

class StateBag 
{ 
    public string Name; 
    public string Value; 
} 

class Program 
{ 
    static List<StateBag> _concurrent = new List<StateBag>(); 

    static void Main() 
    { 
     var sw = new Stopwatch(); 
     try 
     { 
      sw.Start(); 
      Thread thread1 = new Thread(new ThreadStart(A)); 
      Thread thread2 = new Thread(new ThreadStart(B)); 
      thread1.Start(); 
      thread2.Start(); 
      thread1.Join(); 
      thread2.Join(); 
      sw.Stop(); 
     } 
     catch (Exception ex) 
     { 
     } 


     Console.WriteLine("Average: {0}", sw.ElapsedTicks); 
     Console.ReadKey(); 
    } 

    private static Object thisLock = new Object(); 

    public static string GetValue(List<StateBag> stateBagList, string name) 
    { 
     string retValue = string.Empty; 


     if (stateBagList != null) 
     { 
      lock (thisLock) 
      { 
       foreach (StateBag stateBag in stateBagList) 
       { 
        if (stateBag.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) 
        { 
         retValue = stateBag.Value; 
        } 
       } 
      } 

     } 



     return retValue; 
    } 

    static void A() 
    { 
     for (int i = 0; i < 5000; i++) 
     { 
      _concurrent.Add(new StateBag() { Name = "name" + i, Value = i.ToString() }); 
     } 
    } 

    static void B() 
    { 
     for (int i = 0; i < 5000; i++) 
     { 
      var t = GetValue(_concurrent, "name" + i); 
     } 
    } 
} 
+0

传递给此方法的stateBagList可能会在其他地方更改。 –

+0

我猜想这个方法可能是从1个线程调用的,而List则是从另一个线程同时修改的。 – Baldrick

+0

我建议你只为stateBagList使用一个ConcurrentDictionary,并将它的Name(如果它是唯一的)键入。这将比连续搜索List更高效,并且将消除对自己的锁定逻辑的需要。 – Baldrick

回答

8

入门集合被修改;枚举操作可能不会执行。例外

原因:当你通过循环枚举在同一线程或其他线程被修改,就会出现此异常。

现在,在您提供的代码中没有任何这样的场景。这意味着您可能会在多线程环境中调用此方法,并且在其他某个线程中修改了集合。

解决方案:在您的枚举上实现锁定,以便一次只有一个线程可以访问。像这样的东西应该这样做。

private static Object thisLock = new Object(); 
public static string GetValue(List<StateBag> stateBagList, string name) 
{ 
    string retValue = string.Empty; 

    if (stateBagList != null) 
    { 
     lock(thisLock) 
     { 
      foreach (StateBag stateBag in stateBagList) 
      { 
       if (stateBag.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) 
       { 
       retValue = stateBag.Value; 
       } 
      } 
     } 
    } 

    return retValue; 
} 
+0

Ya在覆盖线程环境中运行,但通过添加锁定枚举可能会减慢我的应用程序 –

+1

@PravinBakare查看我更新的答案。但它会运行,我认为慢比总是运行更好 – Ehsan

+1

@NoOne将'thisLock'改为静态。 – Nico

1

这是因为您的应用程序中的某些其他线程正在修改stateBagList。有两件事你可以做......要么在你引用stateBagList的代码块中使用锁定,要么你可以在GetValues方法中创建一个stateBagList的深层副本,然后在你的for循环中使用新的列表。

3

尽管锁定是修复原始实现的正确方法,但可能会有更好的方法,这会涉及更少的代码和潜在的错误。

以下演示控制台应用程序使用ConcurrentDictionary而不是List,并且完全是线程安全的,无需您自己的锁定逻辑。

它也提供了更好的性能,因为字典查找是不是串行搜索的列表要快得多:

class StateBag 
{ 
    public string Name; 
    public string Value; 
} 

class Program 
{ 
    public static string GetValue(ConcurrentDictionary<string, StateBag> stateBagDict, string name) 
    { 
     StateBag match; 
     return stateBagDict.TryGetValue(name.ToUpperInvariant(), out match) ? 
      match.Value : string.Empty; 
    } 

    static void Main(string[] args) 
    { 
     var stateBagDict = new ConcurrentDictionary<string, StateBag>(); 

     var stateBag1 = new StateBag { Name = "Test1", Value = "Value1" }; 
     var stateBag2 = new StateBag { Name = "Test2", Value = "Value2" }; 

     stateBagDict[stateBag1.Name.ToUpperInvariant()] = stateBag1; 
     stateBagDict[stateBag2.Name.ToUpperInvariant()] = stateBag2; 

     var result = GetValue(stateBagDict, "test1"); 

     Console.WriteLine(result); 
    } 
} 
0

正如已经建议,你需要周围放置枚举的锁。

但是,只有在锁定正在修改集合的语句时,该操作才有效。

static void A() 
{ 
    for (int i = 0; i < 5000; i++) 
    { 
     lock(thisLock) 
     { 
      _concurrent.Add(new StateBag() { Name = "name" + i, Value = i.ToString() }); 
     } 
    } 
} 

否则,您所做的只是确保一次只有一个线程可以枚举集合。单个线程或多个其他线程可能仍然在修改集合,而这个枚举发生。

我还推荐以下链接: http://www.albahari.com/threading/part2.aspx#_Thread_Safety_and_NET_Framework_Types

其他提示: 它可以对集合本身锁,像这样:

lock(_concurrent) { //statements} 

而且GetValue方法可以简化像所以:

public static string GetValue(List<StateBag> stateBagList, string name) 
{ 
    if (stateBagList != null) 
    { 
     lock (thisLock) 
     { 
      return stateBagList.FirstOrDefault 
       (x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); 
      } 
     } 
    }  

    return string.Empty; 
} 
0

List替换为SynchronizedCollection 。它是线程安全的集合类。

它通过锁定来实现这一点,因此您基本上拥有一个List,其中每个访问都被封装在一个锁定语句中。

相关问题