并发问题。锁未释放

时间:2013-12-22 18:25:09

标签: c# multithreading locking

您好,我在处理项目时遇到了一些麻烦 简短摘要:

  • 客户端/服务器应用程序(我在服务器端)
  • 多线程
  • 使用System.Timers.Timer
  • 每秒使用KeepAlive
  • 单独线程上的主要网络循环(向/从客户端读取/写入数据包)
  • 服务器在单独的线程上(此时无关紧要)

我有一个处理所有客户端的ClientHandler类。 (Networker是主循环) CodeMap of ClientHandler ClientList实现如下:

public List<Client> ClientList { get; private set; }

每当我尝试访问ClientList(读/写)时,我都会使用...

lock(ClientList){}
lock(ClientHandler.ClientList){}

...取决于我在ClientHandler中的位置。

到目前为止,我没有使用任何锁,因此存在一些并发问题 但是现在当我使用/误用锁时,我遇到了一些问题 如果客户连接:

public bool AddClient(Client client)
{
    lock (ClientList)
    {
        if (client == null || ClientList.Contains(client))
            return false;

        ClientList.Add(client);
        return true;
    }
}

每一秒我的计时器都会加入一个保持活动:

private void KeepAliveTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    KeepAliveTimer.Stop();
    lock (ClientList)
    {
        if (ClientList.Count > 0)
        {
            foreach (Client client in ClientList)
            {
                lock (client)
                {
                    client.PacketQueue.Enqueue(new KeepAlivePacket());
                }
            }
        }
    }
    KeepAliveTimer.Start();
}

我现在的主循环:

private void Networker()
{
    while (IsRunning)
    {
        lock (ClientHandler.ClientList)
        {
            if (ClientHandler.ClientList.Count > 0)
            {
                foreach (Client client in ClientHandler.ClientList)
                {
                    // Check if client has data to read.
                    // Read for up to 10 msecs.
                    if (client.DataAvailable)
                    {
                        DateTime expiry = DateTime.Now.AddMilliseconds(10);
                        while (DateTime.Now <= expiry)
                        {
                            int id = client.ReadByte();

                            if (id == -1 || !PacketHandler.HandlePacket((byte)id, client, this))
                            {
                                ClientHandler.DisconnectClient(client);
                                continue;
                            }
                        }
                    }


                    // Check if client has data to write.
                    // Write for up to 10 msecs.
                    if (client.PacketQueue.Count > 0)
                    {
                        DateTime expiry = DateTime.Now.AddMilliseconds(10);
                        while (DateTime.Now <= expiry && client.PacketQueue.Count > 0)
                        {
                            IPacket packet = client.PacketQueue.Dequeue();
                            if (!packet.Write(client))
                            {
                                ClientHandler.DisconnectClient(client);
                                continue;
                            }
                        }
                    }

                }
            }
        }

        Thread.Sleep(1);
    }
}

在所有这些锁定之前,我的测试客户端每秒都有一个KeepAlivePacket 现在我只得到它一次,因为在第一个KeepAlivePacket后,KeepAliveTimer_Elapsed不能再访问锁,因为它被其他一些线程永久锁定(用一些调试输出测试)。

提供的代码中是否有某些东西可能是疯子或者其他我做错了什么?

如果有人能让我摆脱这种痛苦,那将会很棒。

编辑(感谢Joachim Isaksson):
我不知道这是否是唯一的错误,但我忘记的一件事是在我读完第一个数据包之后检查主循环是否有可用的数据。
这是第一个问题,因为我只用我的TestClient发送一个数据包,服务器卡在client.ReadByte,因为事先没有检查。

if (client.DataAvailable)
{
    DateTime expiry = DateTime.Now.AddMilliseconds(10);
    while (DateTime.Now <= expiry && client.DataAvailable)
    {
        try
        {
            int id = client.ReadByte();
            // do stuff...
        }...
    }
}

2 个答案:

答案 0 :(得分:1)

为什么不在System.collections.concurrent中使用集合而不是自己锁定?

答案 1 :(得分:1)

封装资源并为每个共享资源使用单独的锁定对象。锁定集合实例(共享资源)或其所有者实例被视为不良做法(!)。

然后,添加所有必要的方法来操作拥有私有集合实例的对象的集合。

readonly List<MyItemClass> _myItems = new List<MyItemClass>();
readonly object lockObject = new object();

public IEnumerable<MyItemClass> MyItems
{
    get
    {
         lock(lockObject)
         {
              return _myItems.ToArray();
         }
    }
}

public void Add(MyItemClass item)
{
    lock(lockObject)
    {
        _myItems.Add(item);
    }
}

public bool Remove(MyItemClass item)
{
    lock(lockObject)
    {
        return _myItems.Remove(item);
    }
}

这给我带来了很多挫折感。

偏离主题,但有些相关:如果你的集合是一个ObservableCollection,你可以调用静态方法

BindingOperations.EnableCollectionSynchronization(_myItems, lockObject)

在您的所有者实例构造函数中。 这将确保即使WPF的绑定机制在另一个线程上更新,它甚至可以枚举该集合。它枚举列表时使用与Add和Remove方法相同的锁定对象(重绘项列表控件等)。静态方法是.NET 4.5的新增方法,无需从ViewModel向UI线程发送可观察的集合更改。