使用lock()的C#事件侦听器仍然抛出异常

本文关键字:侦听器 抛出异常 事件 lock 使用 | 更新日期: 2023-09-27 18:29:44

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

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

这两个事件以及update方法都会为线程安全锁定列表。但它仍然抛出一个异常,表示在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);
    }
}

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

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

使用lock()的C#事件侦听器仍然抛出异常

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语句

中无法更新或删除它们

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

这种设计的原因是一个线程一次可以做一件事。。。因此,如果线程已经有锁,重新输入锁是可以的,因为它不可能同时在两个位置。。。因此,对于目前有权访问锁的单线程来说,线程安全问题的风险并不存在——它不能像单线程应用程序是线程安全的那样与自己竞争。

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