2013-12-22 42 views
1

嗨,那里我在处理我的项目时感到头疼。
简短的摘要:
并发问题。锁没有发布

  • 客户端/服务器应用程序(我是在服务器端)
  • 多线程
  • 保持活动分分秒秒与System.Timers.Timer在单独的线程
  • 主要网络环路(读/写数据包到客户端/从客户端写数据包)
  • 服务器在单独的线程上(此时无所谓)

我有一个处理所有客户端的ClientHandler类。 (Networker的是主循环) CodeMap of ClientHandler 客户端列表中,因为这实现:

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

每次我尝试访问客户端列表(读/写)我用...

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

...依据如果我在ClientHandler内部或外部。

到目前为止,我没有使用任何锁,所以有一些并发问题。
但现在,当我使用/误用锁时,Keepalive遇到了一些问题。
如果一个客户端连接:

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无法再访问该锁,因为它被其他线程永久锁定(用一些调试输出测试它)。

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

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

编辑(感谢约阿希姆伊萨克森):
我不知道这是否是唯一的错误,但有一件事我忘了是在主循环,以检查是否有可用的数据后,我读的第一个包。
这是第一个问题,因为我只发送一个数据包与我的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... 
     }... 
    } 
} 
+1

与论坛网站不同,我们不使用“谢谢”或“任何帮助表示赞赏”,或在[so]上签名。请参阅“[应该'嗨','谢谢',标语和致敬从帖子中删除?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be –

+1

好吧,谢谢你会记住:) – TorbenJ

+1

不知道你正在调用哪个'ReadByte',但我怀疑它会等待数据可用,如果没有,它会锁定,直到有,并且它永远不会释放锁 –

回答

1

你为什么不使用的,而不是做你自己锁定在System.collections.concurrent收藏?

+0

这将是一种替代方案,但我想知道我在做什么错了,以便学习所有这些东西。我不喜欢它总是使用高级类来避免学习理论如何独立完成。 – TorbenJ

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方法相同的锁定对象(重绘itemlists控件等)。静态方法是.NET 4.5的新增功能,无需从ViewModel调度UI线程的可观察集合更改。