我试图了解学校作业的线索,我试图让两个线程清空一个集合。到目前为止我提出的代码抛出一个异常,说该集合已被修改。
首先我在锁定的代码部分中有一个while循环,但是然后(当然;-))只有一个线程清空了该集合。
我的问题是,我怎么能有一个循环,让线程轮流清空集合?
class Program
{
private static List<int> containers = new List<int>();
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
containers.Add(i);
}
Thread t1 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
t1.Name = "Kraan 1";
t1.Start();
Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
t2.Name = "Kraan 2";
t2.Start();
Console.Write("Press any key to continue...");
Console.Read();
}
static void GeefContainer()
{
lock (containers)
{
int containerNummer = containers.Count - 1;
//Container container = containers[containerNummer];
//Console.Write("Container {0} opgehaald... Overladen", containerNummer);
Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);
//Gevaarlijk, want methode aanroepen kan klappen
containers.RemoveAt(containerNummer);
}
}
}
答案 0 :(得分:2)
我假设您不允许使用System.Collections.Concurrent命名空间中的任何ThreadSafe集合。
在检查是否还有条目时,您需要获得对容器集合的独占访问权限。但是,在释放锁定之前,您不希望1个线程采取独占控制来删除所有条目。 Monitor.Pulse可用于允许等待锁定容器的其他线程首先进入&#39;。尝试以下GeefContainers实现:
static void GeefContainer()
{
lock (containers)
{
while (containers.Any()) // using linq, similar to: while(container.Count > 0)
{
containers.RemoveAt(0); // remove the first element
// allow other threads to take control
Monitor.Pulse(containers); // http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx
// Wait for a pulse from the other thread
Monitor.Wait(container);
}
}
}
哦,从中删除你的循环逻辑:
Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
只需调用GeefContainer即可。
这可以通过以下方式显示:
等
答案 1 :(得分:1)
您看到的异常是由枚举器抛出的。标准集合上的枚举器进行检查以确保在枚举操作过程中未修改集合(在您的情况下通过foreach
)。
由于您希望让线程交替从集合中移除,因此您需要某种允许线程相互发信号的机制。我们还必须注意不要同时从多个集合中访问集合。即使Count
属性在没有同步的情况下也可以安全使用。 Barrier类使信令非常简单。一个简单的lock
就足以实现同步。我就是这样做的。
public class Program
{
public static void Main(string[] args)
{
var containers = new List<int>();
for (int i = 0; i < 100; i++)
{
containers.Add(i);
}
var barrier = new Barrier(0);
var t1 = new Thread(() => GeefContainers(containers, barrier));
t1.Name = "Thread 1";
t1.Start();
var t2 = new Thread(() => GeefContainers(containers, barrier));
t2.Name = "Thread 2";
t2.Start();
Console.Write("Press any key to continue...");
Console.Read();
}
private static void GeefContainers(List<int> list, Barrier barrier)
{
barrier.AddParticipant();
while (true)
{
lock (list)
{
if (list.Count > 0)
{
list.RemoveAt(0);
Console.WriteLine(Thread.CurrentThread.Name + ": Count = " + list.Count.ToString());
}
else
{
break;
}
}
barrier.SignalAndWait();
}
barrier.RemoveParticipant();
}
}
Barrier
类基本上会导致这种情况一次又一次地发生。
|----| |----| |----|
| T1 |-->| |-->| T1 |-->| |-->| T1 |
|----| | | |----| | | |----|
|-->(B)-->| |-->(B)-->|
|----| | | |----| | | |----|
| T2 |-->| |-->| T2 |-->| |-->| T2 |
|----| |----| |----|
在上图中,T1
和T2
分别代表线程1和2上的删除操作。 (B)
表示对Barrier.SignalAndWait
的调用。
答案 2 :(得分:0)
如果您按如下方式修改线程怎么办?这样,两个线程都应该花些时间对集合执行操作。
Thread t1 = new Thread(() => {
while (containers.Count > 0)
{
GeefContainer();
Thread.Sleep(150);
}});
t1.Name = "Kraan 1";
t1.Start();
Thread t2 = new Thread(() => {
while (containers.Count > 0)
{
GeefContainer();
Thread.Sleep(130);
}});
t2.Name = "Kraan 2";
t2.Start();
答案 3 :(得分:0)
首先,按如下方式更改您的定义:
new Thread(() => { while(containers.Count>0) { GeefContainer(); } });
然后,按如下方式重写GeefContainer()以避免异常:
static void GeefContainer()
{
lock (containers)
{
int containerNummer = containers.Count - 1;
if(containerNummer>=0)
{
//Container container = containers[containerNummer];
//Console.Write("Container {0} opgehaald... Overladen", containerNummer);
Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);
//Gevaarlijk, want methode aanroepen kan klappen
containers.RemoveAt(containerNummer);
}
}
}