获取集合已被修改;枚举操作可能无法执行。例外
代码:
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 资源)
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at Tavisca.TravelNxt.Shared.Entities.StateBag.GetValue(List`1 stateBagList,String name)
@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 :(得分:10)
获取集合已被修改;枚举操作可能无法执行。例外
原因:当您循环的枚举在同一个线程或其他某个线程中被修改时,会发生此异常。
现在,在您提供的代码中,没有任何此类情况。这意味着您可能在多线程环境中调用它,并且在其他一些线程中修改了集合。
解决方案:在枚举上实施锁定,以便一次只能访问一个线程。这样的事情应该做到。
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;
}
答案 1 :(得分: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);
}
}
答案 2 :(得分:1)
这种情况正在发生,因为应用程序中的某些其他线程正在修改stateBagList。你可以做两件事......要么在你引用stateBagList的代码块周围使用锁定,要么你可以在GetValues方法中制作一个stateBagList的深层副本,然后在for循环中使用新的列表。
答案 3 :(得分: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;
}
答案 4 :(得分:0)
将List
替换为SynchronizedCollection。它是线程安全的集合类。
它通过锁定来实现这一点,因此你基本上有一个List
,其中每个访问都包含在一个锁定语句中。