【问题标题】:C# event listener using lock() still throws exceptions使用 lock() 的 C# 事件监听器仍然抛出异常
【发布时间】:2013-09-01 05:58:46
【问题描述】:

我正在做一个xna游戏,问题涉及到以下几个类:
游戏类——这个类监听下面两个类的事件监听器
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);
    }
}

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

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

【问题讨论】:

    标签: c# list events exception xna


    【解决方案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 声明中更新或删除它们

    【讨论】:

    • 谢谢,我考虑了更多,只有从列表中添加或删除项目的事件才会导致问题,所以我会给实体一些在更新调用后检查的属性(仍在foreach 循环)将要删除/添加的项目添加到单独的列表中,然后枚举那些
    【解决方案2】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2021-11-03
      • 2013-09-14
      • 2019-05-18
      • 1970-01-01
      • 1970-01-01
      • 2021-12-02
      • 1970-01-01
      • 2016-09-03
      • 2020-02-02
      相关资源
      最近更新 更多