我正在尝试从多个线程添加到哈希集。如果该项已存在,我想更新它,如果它不存在,我想将它添加到列表中。
使用我正在使用的代码我最终会有很多重复,我认为因为多个项目突然指向相同的引用。我无法看到这种情况发生在何处或为何会发生。
下面是我正在使用的代码,后面是我第一次看到问题时结束的“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 As Requested虽然我认为问题不在这里,因为我使用的是带有手动检查的列表,但也存在问题。
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 :(得分:2)
以下两个UUIDs
将具有不同的哈希码,但比较相等:“x”,“x”。原因:您正在以不同方式处理空白。
您需要GetHashCode
和Equals
。如果Equals
返回true,则两个哈希码必须相同。如果您未能遵守此合同,HashSet
将以未定义的方式运行(可能重复)。
解决方案:两个地方都有Trim
,或者没有。{/ p>
答案 1 :(得分:1)
按照设计,重要的是集合中项目的哈希码永远不会出现在集合内部。该集合无法检测到对象的内部状态发生了变化,因此它将位于“旧桶”的旧桶中,因此当您尝试使用新的哈希码添加另一个项时,它将看到它是“ bucket“为空并添加项目。如果你想要在项目中当前“更改”项目,你应该删除它,更改它,然后将其重新添加。或者,更好的(从设计角度来看)删除旧值,并添加一个新对象完全(可能从删除的某个方面复制某些方面)。
看来这是你的问题;我会在下面留下我的其他建议,尽管它们不是你遇到的问题。
您的RemoteDevice
类可能无法覆盖Equals
和GetHashCode
并实施有意义的实施。默认实现(在object
中定义)仅基于对象内存中的地址,因此具有所有相同值的两个不同实例将与该定义“不相等”。因为它似乎有一个单个GUID作为唯一ID有效(GUID具有合理的Equals
和GetHashCode
定义)您的实现应该遵循这一点。
即:
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
之后再进入他们。这意味着在任何人访问lock
之前,您的代码需要在对象的同一实例上HashSet
(因为HashSet不是设计为由多个线程使用)。
如果这样做不是一个选项,或者是不可取的,那么你需要考虑制作一个可以从多个线程访问的集合。许多集合都在System.Collections.Concurrent
中有一个实现,但遗憾的是没有ConcurrentSet
。有几种选择;我们可以自己制作一个,但另一种选择是使用ConcurrentDictionary
并简单地忽略这些值并只使用键。这将是一个混乱,但有效地创建自己的并发集合是很难的。就个人而言,如果我想使用一个,我只需在ConcurrentDictionary
周围创建一个包装器,隐藏它存储对的事实。
答案 2 :(得分:0)
使用Dictionary,将使您的代码更简单,更健壮,并且不依赖于GetHashCode覆盖。查找也会快得多,LINQ查询的性能并不是很好。 应该注意简单的事情,比如不多次计算相同的值,特别是如果它在一个循环中。即在LINQ查询中多次调用notifyMessage.UUID.Trim(),因为许多设备都在列表中。 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查询实际上是一个完整的迭代,因为使用Where而不是FirstOrDefault和谓词,除非编译器足够聪明地将表达式转换为FirstOrDefault(d =&gt; d.UUID.Trim()。Equals(notifyMessage.UUID.Trim( ),StringComparison.OrdinalIgnoreCase)。