LinkedList已修改,Thread崩溃程序

时间:2013-12-02 15:29:47

标签: c# multithreading concurrency synchronize

我的问题是线程同步问题,用户同时访问和修改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。有了这个,当用户添加或删除消息时,我不应该没有问题地迭代列表吗?

有没有办法同步访问列表?

编辑:我已经尝试了ConcurrentBagConcurrentDictionary,但项目没有任何改进,正如您在评论中看到的那样......

请在发布回答之前阅读下面的评论,以确保您知道发生了什么。

编辑:尝试在addMsgremoveMsg的代码中添加互斥锁,但仍然崩溃。如果我在线程中使用互斥锁,它将会减慢速度。

4 个答案:

答案 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();
}

现在,您可以安全地对其进行枚举,而不必担心不一致。

一些注意事项:

  • 我正在使用锁,因为克隆也必须枚举你的集合,虽然它在很短的时间内完成,但它仍然需要同步。当然,您需要取消注释已添加到addMsg和removeMsg的锁。
  • 锁定整个循环不起作用的原因是,当您调用Invoke时,您实际上是将控制权返回给拥有该对象的线程(在本例中为主GUI线程)。问题是,这个线程已经停留在处理导致addMsg或removeMsg被调用的任何事件上,导致死锁。
  • 您还应该注意每30毫秒克隆一个集合,效率不高,不应该在生产代码中使用,但考虑到这可能只是一个练习,它应该满足。在现实生活中,您可能应该使用单独的集合来处理要执行的更改(添加或删除标签),在addMsg和removeMsg中更改此集合,然后将更改合并到线程内的标签,但在迭代之外在标签上。
  • 与您的问题没有直接关系,但仍然:您应该使用foreach循环,而不是直接在C#中创建枚举器对象。

答案 3 :(得分:0)

如前所述,在枚举时更改任何集合会导致.Net中出现异常。您可以通过使用for或while循环来避免这种情况。

但是,在这种情况下,我没有看到使用链接列表的重点。使用ConcurrentDictionary并添加或删除请求的项目应该更简单,更高效。还有一个ObservableConcurrentDictionary,虽然不是Framework的一部分。根据我的经验,这是非常稳定的。 http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So