【问题标题】:How to properly lock a collection如何正确锁定集合
【发布时间】:2021-05-22 13:01:50
【问题描述】:

我一直想把头绕在锁上,但我似乎无法弄清楚。 下面的代码使用了锁,但仍然给出“集合已修改”错误。我错过了什么?


    class Program
    {
        static List<int> lEntries = new List<int>();
        static readonly object entriesLock = new object();

        public static List<int> Entries {
            get { lock (entriesLock) { return lEntries; } }
            set { lock (entriesLock) { lEntries = value; } }
        }

        static void Main(string[] args)
        {
            // Run 20 times to reproduce the issue more often
            for (int i = 0; i < 20; i++)
            {
                Task.Run(() =>
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        Entries.Add(j);
                    }
                });

                Task.Run(() =>
                {
                    for (int i = 0; i < 1000; i++)
                    {
                        Entries.Average(); // System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
                    }
                });
            }

            Console.ReadLine();
        }
    }


【问题讨论】:

  • 我不知道你项目的用例。但我更推荐使用:ConcurrentBag.
  • 这是个好建议。这个例子稍微简化了一点,因为我实际上使用的是 SortedList。也许还有一个并发集合。
  • @M1sterPl0w 请不要推荐ConcurrentBag&lt;T&gt;。这是一个extremely specialized 集合。它不是线程安全的List&lt;T&gt;ConcurrentQueue&lt;T&gt; 更可取,也不是线程安全的List&lt;T&gt;
  • Jens 我已经向here 发布了一些关于如何使用锁的指南,您可能会觉得这些指南很有用。请注意,即使将List&lt;T&gt; 替换为ConcurrentQueue&lt;T&gt; 之类的并发集合,在此集合上调用Average LINQ 运算符仍将违反线程安全。只有这些集合的公共成员是线程安全的。通过这些集合实现的任何接口(包括扩展方法)访问的成员不能保证是线程安全的。
  • @TheodorZoulias 没有 ConcurrentBag 是线程保存...docs.microsoft.com/en-us/dotnet/api/…。我引用:“代表一个线程安全的、无序的对象集合。”。我不知道用例,我试图让 OP 找到他的解决方案,因此有更多的并发对象。当您搜索 ConcurrentBag 或 ConcurrentQueue 时,您会发现更多类似这样的数据类型...

标签: c# .net concurrency locking


【解决方案1】:

锁只在其作用域内持续存在。

lock (entriesLock)
{
  //safe to access here.
}
// no longer safe

因此,不幸的是,您返回锁定列表的尝试毫无意义,因为当 getter/setter 离开时,锁定会立即过期。实际访问列表时使用外部锁。

for (int j = 0; j < 10000; j++)
{
  lock (entriesLock)
  {
    lEntries.Add(j);
  }
}

// or

lock (entriesLock)
{
  for (int j = 0; j < 10000; j++)
  {
    lEntries.Add(j);
  }
}

【讨论】:

  • 哦,我现在明白了,这是我的疏忽。我会尽快将其标记为答案。
猜你喜欢
  • 2019-10-22
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 2017-03-23
  • 2015-10-31
  • 1970-01-01
  • 2011-07-31
  • 1970-01-01
相关资源
最近更新 更多