从多个线程添加到HashSet时出错

时间:2012-11-21 18:48:58

标签: c# .net c#-4.0

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

使用我正在使用的代码我最终会有很多重复,我认为因为多个项目突然指向相同的引用。我无法看到这种情况发生在何处或为何会发生。

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

3 个答案:

答案 0 :(得分:2)

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

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

解决方案:两个地方都有Trim,或者没有。{/ p>

答案 1 :(得分:1)

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

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

您的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之后再进入他们。这意味着在任何人访问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)。