收集被修改;枚举操作可能无法执行。例外

时间:2014-01-10 04:17:30

标签: c#

获取集合已被修改;枚举操作可能无法执行。例外

代码:

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);
        }
    }
}

5 个答案:

答案 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,其中每个访问都包含在一个锁定语句中。