2

我正在制作一个 xna 游戏,问题涉及以下类:
Game 类 - 该类侦听
Player 类下面两个类的事件侦听器 - 该类启动 Fire() 事件,告诉游戏玩家发射子弹
Bullet类 - 这个类启动一个 SelfDestruct() 事件(经过一定距离后),告诉游戏该实例必须被删除

游戏类有一个子弹列表,在更新方法中它在这个列表上执行一个 foreach
事件Fire() 的监听器将新的项目符号添加到项目符号列表 SelfDestruct() 的事件监听器从列表中删除发送者(通过强制转换为项目符号)

这两个事件以及更新方法都会锁定列表以确保线程安全。但它仍然抛出异常,告诉列表在 foreach 期间被修改。

我该如何解决这个问题;因为我确实锁定了列表..但这不起作用:

Update:
public void Update(GameTime gameTime)
{
    player.Update(GameTime gameTime);//can throw fire event
    lock(Bullets)//Lock the list for thread safett
    {
        foreach(Bullet b in Bullets)//Throws exception when bullet is added/removed
            b.Update(gameTime);//can throw selfdestruct event
    }
}

Fire listener:
void listen_fire(object sender, EventArgs e)
{
    Player p = (Player)sender;/used to get coordinates and rotation stored in the player
    lock(Bullets)
    {
        Bullets.Add(new Bullet(p.Position,p.Rotation));
    }
}

Self destruct listener:
void listen_selfdestruct(object sender, EventArgs e)
{
    lock(Bullets)
    {
        Bullets.Remove((Bullet)sender);
    }
}

我认为这个解决方案可能会失败,因为事件被抛出在一个线程中,该线程本身已经锁定了列表

欢迎任何解决方案,感谢您阅读我的问题

4

2 回答 2

1

foreach 中使用的集合是不可变的。这在很大程度上是设计使然。

正如MSDN上所说:

foreach 语句用于遍历集合以获取您想要的信息,但不能用于从源集合中添加或删除项目以避免不可预知的副作用。如果您需要从源集合中添加或删除项目,请使用 for 循环。

例如这段代码会抛出异常:

     List<string> lst = new List<string>();

     lst.Add("aaa");
     lst.Add("bbb");

     foreach (string curr in lst)
     {
        if (curr.Equals("aaa"))
        {
           lst.Remove(curr);
        }
     }

所以我这样做是为了在迭代时不会从列表中删除:

     List<string> lst = new List<string>();

     lst.Add("aaa");
     lst.Add("bbb");
     List<string> lstToDel = new List<string>();

     foreach (string curr in lst)
     {
        if (curr.Equals("aaa"))
        {
           lstToDel.Add(curr);
        }
     }

     foreach (string currToDel in lstToDel)
     {
        lst.Remove(currToDel);
     }

现在我不知道您的物品中有哪些物品,但您在声明Bullets中无法更新或删除它们foreach

于 2013-09-01T06:07:06.740 回答
0

当已经提升到该线程的其他lock语句时,将忽略同一线程上的其他语句lock。您foreach最初锁定了该线程的代码,阻塞了所有其他线程,但是代码可能会触发一个事件(不是异步的),该事件也会锁定......但是由于该事件将与 foreach 位于同一线程上,因此代码可以在事件中输入锁定块,并且您尝试从您正在枚举的列表中删除,就好像您根本没有锁定一样。

这种设计的原因是一个线程一次只能做一件事......因此,如果线程已经拥有锁,则重新进入锁是可以的,因为它不能同时在两个地方......因此当前有权访问锁的单线程不存在线程安全问题的风险——它不能像单线程应用程序是线程安全的那样与自己竞争。

为避免这种情况,请考虑使用线程安全集合,或将工作委派给工作线程/任务。

于 2013-09-01T06:24:38.843 回答