简体   繁体   中英

Concurrency problems. Locks not released

Hi there I got some headaches while working on my project.
Short summary:

  • Client/server application (I'm on the server side)
  • Multithreaded
  • KeepAlive every second with System.Timers.Timer
  • Main networking loop on separate thread (reads/writes packets to/from clients)
  • Server on separate thread (doesn't matter at this point)

I have a ClientHandler class which handles all clients. (Networker is the main loop) ClientHandler的CodeMap ClientList is implemented as this:

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

Everytime I try to access ClientList (read/write) I use...

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

... depending where if I'm in or outside ClientHandler.

Up to now I didn't use any locks so there were some concurrency problems.
But now as I'm using / misusing locks I got some problems with the keepalives.
If a client connects:

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

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

And every second my timer enqueues a keepalive:

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

And my current main loop:

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);
    }
}

Before all these locks my test client got a KeepAlivePacket every second.
Now I'm getting it only once because after the first KeepAlivePacket KeepAliveTimer_Elapsed can't access the lock anymore because its permanently locked by some other thread (tested it with some debug output).

Is there something in the provided code that could be the mad guy or is there something else I'm doing completely wrong?

Would be great if someone could get me out of this misery.

Edit (thanks to Joachim Isaksson):
I don't know if it was the only bug but one thing I forgot is to check in the mainloop if there is data available after I read the first packet.
That was the first problem because I only send one packet with my TestClient and the server got stuck at client.ReadByte because there was no check beforehand.

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

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

Encapsulate your resources and use a separate lock object for each shared resource. Locking on the collection instance (shared resource) or its owner instance is considered bad practice(!).

Then, add all necessary methods to manipulate your collection on the object owning the private collection instance.

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);
    }
}

This has saved me a lot of frustration.

Off topic, but somewhat related: If your collection is an ObservableCollection, you can put a call to the static method

BindingOperations.EnableCollectionSynchronization(_myItems, lockObject)

in your owner instance constructor. This will ensure that even the binding mechanism of WPF can enumerate the collection even if it is updated on another thread. It uses the same locking object as the Add and Remove methods when it enumerates the list (redrawing itemlists controls, etcetera). The static method is new to .NET 4.5 and eliminates the need to Dispatch observable collection changes to the UI thread from the ViewModel.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM