I’m trying to add to a hash set from multiple threads. If the item already exists I want to update it, if it doesn’t exist I want to add it to the list.
With the code I’m using I am ending up with lots of duplicates, I presume because multiple items are suddenly pointing to the same reference. I can’t see where or why this is happening though.
Below is the code I’m using followed by the “Log” string ending when I first see the issue. You can see that suddenly all the items already added have the same value.
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
Update: Here is GetHashCode And Equals As Requested although I don’t think the issue lies here as I was using a list with a manual check and also had issues.
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();
}
By design, it’s important that the hash code of an item in the set never chance while it’s inside of the set. The set has no way of detecting that the internal state of the object changed, so it will be in the “bucket” for it’s old hash value, so when you try to add another item with the new hash code it will see that it’s “bucket” is empty and add the item. If you want to “change” an item while it’s currently in the set you should remove it, change it, and then add it back in. Or, better yet (from a design perspective) remove the old value, and add a new object entirely (possibly with certain aspect copied from the removed one).
It appears that was your issue; I’ll leave the rest of my suggestions below despite them not being the issue you encountered.
Your
RemoteDeviceclass possibly doesn’t overrideEqualsandGetHashCodewith meaningful implementations. The default implementations (defined inobjectare based just on the address in memory of the object, so two different instances of with all of the same values will be “not equal” by that definition. Since it seems you have a single GUID valid as a unique ID (GUID has sensibleEqualsandGetHashCodedefinitions) your implementation should just defer to that.i.e.:
It also appears that you misunderstand how
lockworks. Usinglock(myObject)doesn’t prevent any other object from ever usingmyObject. All it does is cause anyone else trying tolockon that same instance it wait until you exit yourlockbefore they can enter theirs. This means that your code needs tolockon the same instance of an object before anyone accesses theHashSet(since HashSet wasn’t designed to be used by multiple threads).If doing that isn’t an option, or would be undesirable, you’d need to look at making a collection that can be accessed from multiple threads. Many collections have an implementation in
System.Collections.Concurrent, but unfortunately there is noConcurrentSet. There are several options; we could make our own for one, but another option would be to use theConcurrentDictionaryand simply ignore the values and just use the keys. It would be a tad messy, but creating your own concurrent collection effectively is…hard. Personally, if I wanted to use one I’d just create a wrapper aroundConcurrentDictionarythat hide the fact that it stores pairs.