2014-01-10 35 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,其中每個訪問都被封裝在一個鎖定語句中。

相關問題