首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >并发问题。锁未解除

并发问题。锁未解除
EN

Stack Overflow用户
提问于 2013-12-22 18:25:08
回答 2查看 119关注 0票数 1

嗨,我在做我的项目时感到头疼。

摘要:

  • 客户机/服务器应用程序(我在服务器端)
  • 多线程
  • KeepAlive每一秒与System.Timers.Timer
  • 独立线程上的主网络循环(向客户端/从客户端读取/写入数据包)
  • 服务器在单独的线程上(在这一点上并不重要)

我有一个处理所有客户端的ClientHandler类。(网络工作者是主回路)

ClientList的实现如下:

代码语言:javascript
运行
复制
public List<Client> ClientList { get; private set; }

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

代码语言:javascript
运行
复制
lock(ClientList){}
lock(ClientHandler.ClientList){}

..。取决于我在ClientHandler内外的位置。

到目前为止,我没有使用任何锁,因此存在一些并发问题。

但是现在,当我使用/误用锁的时候,我遇到了一些保持生命的问题。

如果客户端连接:

代码语言:javascript
运行
复制
public bool AddClient(Client client)
{
    lock (ClientList)
    {
        if (client == null || ClientList.Contains(client))
            return false;

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

每一秒我的计时器都会排起一条活的长队:

代码语言:javascript
运行
复制
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();
}

我现在的主回路是:

代码语言:javascript
运行
复制
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上。

代码语言:javascript
运行
复制
if (client.DataAvailable)
{
    DateTime expiry = DateTime.Now.AddMilliseconds(10);
    while (DateTime.Now <= expiry && client.DataAvailable)
    {
        try
        {
            int id = client.ReadByte();
            // do stuff...
        }...
    }
}
EN

回答 2

Stack Overflow用户

发布于 2013-12-22 18:32:08

为什么不使用System.collections.concurrent中的集合来代替自己的锁定呢?

票数 1
EN

Stack Overflow用户

发布于 2013-12-22 18:45:54

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

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

代码语言:javascript
运行
复制
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,则可以对静态方法进行调用。

代码语言:javascript
运行
复制
BindingOperations.EnableCollectionSynchronization(_myItems, lockObject)

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20732586

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档