2012-11-21 39 views
1

我試圖從多線程添加到一個哈希集。如果該項目已存在,我想更新它,如果它不存在,我想將它添加到列表中。從多線程添加到HashSet時出錯

隨着我使用的代碼我結束了很多重複,我推測,因爲多個項目突然指向相同的參考。我不明白這是發生在哪裏或爲什麼。

下面是我使用的代碼,後面跟着「Log」字符串,當我第一次看到問題時結束。您可以看到突然添加的所有項目都具有相同的值。

lock (_remoteDevicesLock) 
{ 
    RemoteDevice rDevice = new RemoteDevice(notifyMessage.UUID, notifyMessage.Location); 
    log += notifyMessage.UUID + " " + rDevice.UUID; 
    if (!_remoteDevices.Add(rDevice)) 
    { 
     log += " Not Added \r\n"; 
     rDevice = (from d in _remoteDevices 
        where d.UUID.Trim().Equals(notifyMessage.UUID.Trim(), StringComparison.OrdinalIgnoreCase) 
        select d).FirstOrDefault(); 
     if (rDevice != null) 
     { 
      //Update Device Expire Time 
     } 
    }        
    else 
    { 
     log += " Added \r\n Current HashSet: \r\n"; 

     foreach (RemoteDevice rd in _remoteDevices) 
     { 
      log += rd.UUID + " \r\n"; 
     } 
    } 
} 


00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 

00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 

00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 
00000000-0000-0001-0001-001cdf885737 

00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-1000-001cdf885737 
00000000-0000-0001-0002-001cdf885737 
00000000-0000-0001-0001-001cdf885737 
00000000-0000-0001-0000-001cdf885737 

00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0002-001cdf885737 00000000-0000-0001-0002-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0001-001cdf885737 00000000-0000-0001-0001-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-0000-001cdf885737 00000000-0000-0001-0000-001cdf885737 Not Added 
00000000-0000-0001-1000-001cdf885737 00000000-0000-0001-1000-001cdf885737 Added 

Current HashSet: 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-0000-001cdf885737 
00000000-0000-0001-1000-001cdf885737 

更新:這裏是的GetHashCode和equals的要求,雖然我不認爲問題就出在這裏,因爲我是用列表,並附有手動檢查,也有問題。

public override bool Equals(object obj) 
{ 
    var other = obj as RemoteDevice; 
    if (other == null) 
    { 
     return false; 
    } 
    else 
    { 
     return UUID.Trim().Equals(other.UUID.Trim(), StringComparison.OrdinalIgnoreCase); 
    } 
} 

public override int GetHashCode() 
{ 
    return UUID.GetHashCode(); 
} 
+0

1.不要鎖定暴露在類之外的對象;您應該創建一個特定的對象來鎖定。2.不要使用字符串連接添加到日誌字符串,使用'StringBuilder'。 – Servy

+0

RemoteDevice.Equals和RemoteDevice.GetHashCode是什麼樣子? – fsimonazzi

+0

你可以檢查這個線程http://stackoverflow.com/questions/4306936/how-to-implement-concurrenthashset-in-net –

回答

1

通過設計,重要的是設置中的項目的哈希碼永遠不會機會。該設置無法檢測到對象的內部狀態發生了變化,因此它將位於「存儲桶」中,因爲它是舊的散列值,因此當您嘗試使用新的散列碼添加另一項時,它會看到它是「存儲桶「爲空並添加項目。如果你想在一個項目中「更改」一個項目,你應該刪除它,改變它,然後重新添加它。或者,更好的是(從設計的角度)刪除舊的值,並添加一個新的對象完全(可能從刪除的某個複製的某個方面)。

看來是你的問題;儘管他們不是您遇到的問題,但我會在下面留下我的其餘建議。

您的RemoteDevice類可能不會覆蓋EqualsGetHashCode有意義的實現。默認的實現(在object中定義的只是基於對象內存中的地址,所以具有所有相同值的兩個不同實例將由該定義「不相等」),因爲它似乎有一個有效的單一GUID一個唯一的ID(GUID有理智EqualsGetHashCode定義)您的實現應該只是推遲到

即:

public class RemoteDevice 
{ 
    public Guid UUID { get; set; } 

    public override bool Equals(object obj) 
    { 
     RemoteDevice other = obj as RemoteDevice; 
     if (other == null) return false; 
     return UUID.Equals(other.UUID); 
    } 

    public override int GetHashCode() 
    { 
     return UUID.GetHashCode(); 
    } 
} 

還顯示出你誤解如何lock作品使用lock(myObject)不會阻止任何。來自曾經使用的其他物體myObject。它所做的只是導致其他人試圖使用lock在同樣的情況下,它會等到你退出你的lock才能進入他們。這意味着在任何人訪問HashSet之前(因爲HashSet不是被設計爲被多個線程使用的),您的代碼需要在對象的同一實例上爲lock

如果這樣做不是一個選項,或者是不可取的,你需要考慮製作一個可以從多個線程訪問的集合。許多集合在System.Collections.Concurrent中都有一個實現,但不幸的是沒有ConcurrentSet。有幾種選擇;我們可以爲自己做一個,但另一種選擇是使用ConcurrentDictionary,並簡單地忽略這些值並只使用鍵。這將是一個混亂,但有效地創建自己的併發收藏是......很難。就個人而言,如果我想使用一個,我只是創建一個圍繞ConcurrentDictionary的包裝,它隱藏了它存儲對的事實。

+0

我已經更新了問題,覆蓋看起來幾乎相同。 – Oli

+0

我仍然對鎖的問題感到困惑。我正在鎖定_remoteDevicesLock而不是_remoteDevices |我鎖定了這段代碼,所以線程一次等待並執行一個,從而使得哈希集線程安全。如果我沒有這樣做,那麼請解釋我爲什麼和我做錯了什麼。 – Oli

+1

Servy-「集合中的某個項目的HashCode從來沒有機會,而它在集合內部」是問題。事實證明(我不會將品牌命名),但是我的無線路由器出於某種原因改變了UUID與Upnp的規格。我想我只需要解決這個問題。 – Oli

2

以下兩個UUIDs將具有不同的哈希碼,但比較爲相等:「x」,「x」。原因:您正在以不同的方式處理空白。

您需要製作GetHashCodeEquals的內容。如果Equals返回true,則兩個哈希碼必須是是相同的。如果您未能遵守本合同,HashSet會以未定義的方式行事(可能有重複)。

解決方法:在兩個地方都可以使用Trim,也可以不使用。

+0

我剛剛在兩個地方都嘗試了修剪,但都沒有發現問題 – Oli

0

使用字典,將使您的代碼更加簡單和健壯,並且不依賴於GetHashCode覆蓋。查找速度也會快得多,LINQ查詢並不是很好的性能表現。 應該注意簡單的事情,比如不要多次計算相同的值,尤其是在循環中。即notifyMessage.UUID.Trim()在LINQ查詢中被多次調用,因爲許多設備都在列表中。 Id應該在循環之前計算一次並重用。

下面是一個示例使用字典:

var _remoteDevices = new Dictionary<string, RemoteDevice>(); 

...

var deviceId = notifyMessage.UUID.Trim().ToLowerInvariant(); 

RemoteDevice remoteDevice; 

if (_remoteDevices.TryGetValue(deviceId, out remoteDevice)) 
{ 
    UpdateDevice(remoteDevice); 
} 
else 
{ 
    var newDevice = CreateDevice(notifyMessage); 

    _remoteDevices.Add(deviceId, newDevice); 
} 

上面的代碼做一個查詢,而不是兩個代碼在你的問題,其中兩個_remoteDevices.Add和LINQ查詢執行查找。 LINQ查詢實際上是一個完整的迭代,因爲除非編譯器足夠聰明地將表達式轉換爲FirstOrDefault(d => d.UUID.Trim()。Equals(notifyMessage.UUID.Trim()),否則使用Where而不是FirstOrDefault作爲謂詞。 ,StringComparison.OrdinalIgnoreCase)