在我的实际应用程序中,我需要迭代集合,但它可以从其他线程更改。所以我需要复制集合来迭代它。我将这个问题重现为一个小例子,但显然我对锁和线程缺乏了解导致System.ArgumentException
。用锁尝试不同的东西,但结果是一样的。
class Program
{
static List<int> list;
static void Main(string[] args)
{
list = new List<int>();
for (int i = 0; i < 1000000; i++)
{
list.Add(i);
if (i == 1000)
{
Thread t = new Thread(new ThreadStart(WorkThreadFunction));
t.Start();
}
}
}
static void WorkThreadFunction()
{
lock (list)
{
List<int> tmp = list.ToList(); //Exception here!
Console.WriteLine(list.Count);
}
}
}
答案 0 :(得分:1)
选项1:
以下是您的代码的修改版本:
class Program
{
static List<int> list;
static void Main(string[] args)
{
list = new List<int>();
for (int i = 0; i < 1000000; i++)
{
lock (list) //Lock before modification
{
list.Add(i);
}
if (i == 1000)
{
Thread t = new Thread(new ThreadStart(WorkThreadFunction));
t.Start();
}
}
Console.ReadLine();
}
static void WorkThreadFunction()
{
lock (list)
{
List<int> tmp = list.ToList(); //Exception here!
Console.WriteLine(list.Count);
}
}
}
这里发生的是你的list
在被转换为另一个列表集合时被修改(其中发生了参数异常)。因此,为了避免这种情况,您需要锁定列表,如上所示。
选项2 :(否lock
)
使用Concurrent集合删除lock
:
using System.Collections.Concurrent;
//Change this line
static List<int> list;
//To this line
static ConcurrentBag<int> list;
并删除所有lock
语句。
答案 1 :(得分:1)
我在你的算法中看到了一些问题,也许你应该重构它。如果使用locks
或ConcurrentBag
类,您应该意识到将整个集合复制到新集合只是为了枚举是非常庞大且非常耗时的操作,并且在此期间您无法操作有效收集。
lock (list)
{
// VERY LONG OPERATION HERE
List<int> tmp = list.ToList(); //Exception here!
Console.WriteLine(list.Count);
}
你真的不应该lock
收集这么长的时间 - 在for
循环结束时你有很多Threads
相互阻挡。您必须使用TPL
classes来实现此方法,不应直接使用Threads
。
您可以选择的另一种情况是通过对集合版本进行双重检查来实现一些optimistic lock-free algorithm,甚至lock-free and wait-free algorithm来存储集合的快照并在方法中检查它。集合访问。 Additional information can be found here
我认为您提供的信息不足以为您提供解决问题的正确方法。
答案 2 :(得分:0)
试过了乔尔的建议。 ConcurrentBag非常慢。锁定每次数百万次迭代似乎效率低下。在这种情况下,事件等待句柄看起来很好(比我的电脑上的锁少3倍)。
class Program
{
static List<int> list;
static ManualResetEventSlim mres = new ManualResetEventSlim(false);
static void Main(string[] args)
{
list = new List<int>();
for (int i = 0; i < 10000000; i++)
{
list.Add(i);
if (i == 1000)
{
Thread t = new Thread(new ThreadStart(WorkThreadFunction));
t.Start();
mres.Wait();
}
}
}
static void WorkThreadFunction()
{
List<int> tmp = list.ToList();
Console.WriteLine(list.Count);
mres.Set();
}
}