2013-12-22 47 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線程的可觀察集合更改。