1

我正在尝试从多个线程添加到哈希集。如果该项目已经存在我想更新它,如果它不存在我想将它添加到列表中。

使用我正在使用的代码,我最终会得到很多重复项,我想是因为多个项目突然指向同一个引用。不过,我看不出发生在哪里或为什么会发生这种情况。

下面是我第一次看到问题时使用的代码,后跟“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 And 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();
}
4

3 回答 3

2

以下两个UUIDs将具有不同的哈希码,但比较相等:“x”,“x”。原因:您对待空白的方式不同。

你需要坚持GetHashCodeEquals坚持。如果Equals返回 true,则两个哈希码必须相同。如果您未能遵守本合同HashSet,则会以未定义的方式行事(可能重复)。

解决方案:要么Trim在两个地方,要么都不在。

于 2012-11-21T19:09:56.453 回答
1

按照设计,重要的是集合中的项目的哈希码在集合内部时永远不会发生。该集合无法检测到对象的内部状态发生了变化,因此它将在“桶”中,因为它是旧的哈希值,所以当您尝试使用新的哈希码添加另一个项目时,它会看到它是“ bucket" 为空并添加项目。如果您想“更改”当前位于集合中的项目,您应该删除它,更改它,然后将其重新添加。或者,更好的是(从设计角度)删除旧值,并添加一个新对象完全(可能从删除的某些方面复制)。

看来这是您的问题;尽管它们不是您遇到的问题,但我会将其余的建议留在下面。

您的RemoteDevice类可能不会覆盖EqualsGetHashCode具有有意义的实现。默认实现(定义object仅基于对象内存中的地址,因此具有所有相同值的两个不同实例将根据该定义“不相等”。因为看起来您有一个有效的 GUID 作为唯一 ID(GUID 具有合理性EqualsGetHashCode定义)您的实现应该遵循这一点。

IE:

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工作原理。Usinglock(myObject)不会阻止任何其他对象使用myObject. 它所做的只是导致其他人试图lock在同一个实例上等待,直到您退出您的实例,lock然后他们才能进入他们的实例。这意味着lock在任何人访问之前,您的代码都需要在对象的同一实例上HashSet(因为 HashSet 并非设计为由多个线程使用)。

如果这样做不是一种选择,或者是不可取的,那么您需要考虑创建一个可以从多个线程访问的集合。许多集合在 中都有实现System.Collections.Concurrent,但不幸的是没有ConcurrentSet。有几种选择;我们可以自己做一个,但另一种选择是使用ConcurrentDictionary并忽略值而只使用键。这会有点混乱,但是有效地创建自己的并发集合是......很难。就个人而言,如果我想使用一个,我只需创建一个包装器ConcurrentDictionary来隐藏它存储对的事实。

于 2012-11-21T18:54:53.200 回答
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 查询实际上进行了完整的迭代,因为使用 Where 而不是 FirstOrDefault 和谓词,除非编译器足够聪明地将表达式转换为 FirstOrDefault(d => d.UUID.Trim().Equals(notifyMessage.UUID.Trim() , StringComparison.OrdinalIgnoreCase)。

于 2014-08-13T21:47:29.500 回答