我的问题是线程同步问题,用户同时访问和修改LinkedList
。
我正在用C#创建一个程序,它将在面板中显示一些消息。我收到一个名为“在枚举器实例化后修改了集合。”的错误,这是因为我在线程访问LinkedList
时添加或删除了消息。
我已经阅读了一些解决方案,但我仍然无法使它们正常工作。我在Enumerator
中使用LinkedList
进行线程工作。我试图在我的代码中进行一些锁定,因此在删除或添加元素时,线程不会迭代列表。我还试图锁定我的列表上的操作的线程。但我所有的尝试都失败了。
这是我项目的一些代码。这个是添加消息:
public void addMsg(MsgForDisplay msg) {
Label lbl = new Label();
lbl.Text = (msg.getMsgText() + " -");
lbl.ForeColor = color;
lbl.Font = textFont;
lbl.BackColor = backg;
lbl.Visible = true;
lbl.AutoSize = true;
lbl.Location = new Point(width(), 0);
//lock(labels) { tried to lock here but failed
labels.AddLast(lbl);
lastLb = lbl;
this.Controls.Add(lbl);
this.Refresh();
//}
}
删除邮件:
public void removeMsg(string msg) {
string remove = msg + " -";
Label lbRemove = null;
//lock(labels) { also tried to lock here
var it = labels.GetEnumerator();
while(it.MoveNext()) {
Label label = it.Current;
if (label.Text.Equals(remove)) {
lbRemove = label;
}
}
labels.Remove(lbRemove);
this.Controls.Remove(lbRemove);
this.Refresh();
//}
}
在我的主题:
中存在问题public void run() {
while (true) {
// lock (labels) { also tried to lock here
var it = labels.GetEnumerator();
while (it.MoveNext()) { // the crash occurs here
Label lb = it.Current;
if (lb.Location.X + lb.Width < 0) {
this.Invoke(new MethodInvoker(() => { this.Controls.Remove(lb); }));
if (labels.Count > 1)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lastLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(2000, 0); }));
lastLb = lb;
this.Invoke(new MethodInvoker(() => { this.Controls.Add(lb); }));
this.Invoke(new MethodInvoker(() => { this.Refresh(); }));
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(leftLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lb.Location.X - 3, lb.Location.Y); }));
leftLb = lb;
}
System.Threading.Thread.Sleep(30);
// }
}
}
正如您所看到的,我正在使用GetEnumerator
我的标签,Java中的内容应该是Iterator
。有了这个,当用户添加或删除消息时,我不应该没有问题地迭代列表吗?
有没有办法同步访问列表?
编辑:我已经尝试了ConcurrentBag
和ConcurrentDictionary
,但项目没有任何改进,正如您在评论中看到的那样......
请在发布回答之前阅读下面的评论,以确保您知道发生了什么。
编辑:尝试在addMsg
和removeMsg
的代码中添加互斥锁,但仍然崩溃。如果我在线程中使用互斥锁,它将会减慢速度。
答案 0 :(得分:4)
我在线程的步骤中创建了一个Timer,解决了崩溃问题。如果你想看到它,这是代码。
private System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private void startThread() {
myTimer.Tick += new EventHandler(timerEvent);
myTimer.Interval = 30;
myTimer.Start();
}
private void timerEvent(object sender, EventArgs e) {
var it = labels.GetEnumerator();
while (it.MoveNext()) {
Label lb = it.Current;
// Label lb = labels.ElementAt(b);
if (lb.Location.X + lb.Width < 0) {
this.Controls.Remove(lb);
if (labels.Count > 1)
lb.Location = new Point(lastLb.Right, 0);
else
lb.Location = new Point(2000, 0);
lastLb = lb;
this.Controls.Add(lb);
this.Refresh();
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
lb.Location = new Point(leftLb.Right, 0);
else
lb.Location = new Point(lb.Location.X - 3, lb.Location.Y);
leftLb = lb;
}
}
答案 1 :(得分:1)
问题的根源在于,当您在标签列表上进行迭代时,您可以调用“删除”或“添加函数”来修改此列表,在迭代它时不允许使用它。而不是这个
var it = labels.GetEnumerator();
while (it.MoveNext()) // the crash occurs here
我建议这样的事情:
for(int i = 0; i < labels.Count; i++)
{
labels.remove(labels[i]); //this is valid of course the count of the list will change
//Here you can add or remove elements from the labels
}
或者您可以先尝试将可移动项目收集到时间列表中,然后将其从原始列表中删除。
答案 2 :(得分:0)
正如其他人已经说过的那样,问题是你在枚举时修改了这个集合。
现在,最简单的解决方法显然不是枚举正在修改的同一个集合。你是怎么做到的?很简单,你只需克隆集合,然后迭代它:
lock (labels)
{
var clone = new LinkedList<Label>(labels);
it = labels.GetEnumerator();
}
现在,您可以安全地对其进行枚举,而不必担心不一致。
一些注意事项:
答案 3 :(得分:0)
如前所述,在枚举时更改任何集合会导致.Net中出现异常。您可以通过使用for或while循环来避免这种情况。
但是,在这种情况下,我没有看到使用链接列表的重点。使用ConcurrentDictionary并添加或删除请求的项目应该更简单,更高效。还有一个ObservableConcurrentDictionary,虽然不是Framework的一部分。根据我的经验,这是非常稳定的。 http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So