C# 锁定时的System.ArgumentException
在我的实际应用程序中,我需要迭代集合,但它可以从其他线程更改。所以我需要复制集合以对其进行迭代。我把这个问题复制到一个小例子中,但显然我对锁和线程的理解不足导致了C# 锁定时的System.ArgumentException,c#,multithreading,locking,C#,Multithreading,Locking,在我的实际应用程序中,我需要迭代集合,但它可以从其他线程更改。所以我需要复制集合以对其进行迭代。我把这个问题复制到一个小例子中,但显然我对锁和线程的理解不足导致了System.ArgumentException。用锁尝试了不同的事情,但结果是一样的 class Program { static List<int> list; static void Main(string[] args) { list = new List<int>
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);
}
}
}
类程序
{
静态列表;
静态void Main(字符串[]参数)
{
列表=新列表();
对于(int i=0;i<1000000;i++)
{
列表.添加(i);
如果(i==1000)
{
线程t=新线程(新线程开始(WorkThreadFunction));
t、 Start();
}
}
}
静态void WorkThreadFunction()
{
锁(列表)
{
List tmp=List.ToList();//此处出现异常!
Console.WriteLine(list.Count);
}
}
}
选项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);
}
}
}
并删除所有
lock
语句。我发现您的算法中存在一些问题,可能您应该重构它。在使用锁
或ConcurrentBag
类的情况下,您应该意识到,将整个集合复制到新集合只是为了枚举,这是一项非常庞大且非常耗时的操作,在此过程中,您无法有效地操作集合
lock (list)
{
// VERY LONG OPERATION HERE
List<int> tmp = list.ToList(); //Exception here!
Console.WriteLine(list.Count);
}
锁(列表)
{
//这里的运作时间很长
List tmp=List.ToList();//此处出现异常!
Console.WriteLine(list.Count);
}
你真的不应该锁定
集合这么长的时间-在for
循环结束时,你有很多线程彼此阻塞。此方法必须使用,不应直接使用线程
您可以选择的另一种情况是,通过对集合版本进行双重检查,甚至通过存储集合的快照并在集合访问的方法中进行检查,来实现其中的一些
我认为你提供的信息不足以建议你解决问题的正确方法。尝试了乔尔的建议。这个包很慢。在数百万次迭代中锁定似乎效率低下。在这种情况下,事件等待句柄似乎很好(比在我的电脑上使用锁所花费的时间少3倍)
类程序
{
静态列表;
静态ManualResetEventSlim mres=新的ManualResetEventSlim(错误);
静态void Main(字符串[]参数)
{
列表=新列表();
对于(int i=0;i<10000000;i++)
{
列表.添加(i);
如果(i==1000)
{
线程t=新线程(新线程开始(WorkThreadFunction));
t、 Start();
mres.Wait();
}
}
}
静态void WorkThreadFunction()
{
List tmp=List.ToList();
Console.WriteLine(list.Count);
mres.Set();
}
}
工作正常,但这会使程序的执行速度降低近5倍。我们如何仅在需要复制列表时阻止它,而不是在每次添加操作时阻止它百万次?您是否尝试过使用来自的线程安全集合?您可以使用ConcurrentBag
并移除锁。您可以比较执行性能。使用ConcurrentBag,执行时间甚至比不使用锁的情况长17倍,比使用锁的情况长3倍:(@Dork,你还在用ConcurrentCollection
锁定某些东西吗?这样的性能损失并不常见。@VMAtm否。删除了所有锁定的语句并将列表更改为ConcurrentBag该异常的消息是什么?实际上我真正的程序很少需要查询集合,所以开销不会很大,但感谢链接,我发现了l很多事情。如果是这样的话,你肯定必须正确定义你的算法,可能是在集合的各个部分上进行迭代。完美。我现在可以对集合进行计数,然后使用for循环对其进行迭代。(在我的情况下,如果在读取时添加元素,这不是问题)。int n=list.Count.list.take(n)抛出相同的异常,因此我需要使用for循环而不是LINQ,但这没关系。只需添加Wait()
和Set()
将导致主线程在运行WorkThreadFunction()
时被阻塞。您将始终看到列表。Count
为1001(直到调用Set()
)因为您的线程现在已同步。这意味着在WorkThreadFunction
完成之前,不会对列表执行任何添加操作。
lock (list)
{
// VERY LONG OPERATION HERE
List<int> tmp = list.ToList(); //Exception here!
Console.WriteLine(list.Count);
}
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();
}
}