2013-04-29 31 views
7

Vì vậy, tôi đã gặp phải một vấn đề thú vị khi tôi nhận các khóa trùng lặp trong Từ điển C# khi sử dụng khóa kiểu PhysicalAddress. Điều này thật thú vị vì nó chỉ xảy ra sau một khoảng thời gian rất dài, và tôi không thể tái tạo nó bằng cùng một mã trong một bài kiểm tra đơn vị trên một cỗ máy hoàn toàn khác. Tôi có thể tái tạo nó một cách đáng tin cậy trên một máy Windows XP SP3 nhưng chỉ sau khi cho phép nó chạy trong nhiều ngày tại một thời điểm, và thậm chí sau đó nó chỉ xảy ra một lần.Các khóa trùng lặp trong từ điển khi sử dụng PhysicalAddress làm khóa

Dưới đây là mã mà tôi đang sử dụng và bên dưới là đầu ra nhật ký cho phần đó của mã.

Code:

private void ProcessMessages() 
{ 
    IDictionary<PhysicalAddress, TagData> displayableTags = new Dictionary<PhysicalAddress, TagData>(); 

    while (true) 
    { 
     try 
     { 
      var message = incomingMessages.Take(cancellationToken.Token); 

      VipTagsDisappeared tagsDisappeared = message as VipTagsDisappeared; 

      if (message is VipTagsDisappeared) 
      { 
       foreach (var tag in tagDataRepository.GetFromTagReports(tagsDisappeared.Tags)) 
       { 
        log.DebugFormat(CultureInfo.InvariantCulture, "Lost tag {0}", tag); 

        RemoveTag(tag, displayableTags); 
       } 

       LogKeysAndValues(displayableTags); 

       PublishCurrentDisplayableTags(displayableTags); 
      } 
      else if (message is ClearAllTags) 
      { 
       displayableTags.Clear(); 
       eventAggregator.Publish(new TagReaderError()); 
      } 
      else if (message is VipTagsAppeared) 
      { 
       foreach (TagData tag in tagDataRepository.GetFromTagReports(message.Tags)) 
       { 
        log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag ({0}) with Exciter Id ({1})", tag.MacAddress, tag.ExciterId); 

        if (tagRules.IsTagRssiWithinThreshold(tag) && tagRules.IsTagExciterValid(tag)) 
        { 
         log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag is displayable ({0})", tag); 

         bool elementAlreadyExists = displayableTags.ContainsKey(tag.MacAddress); 

         if (elementAlreadyExists) 
         { 
          displayableTags[tag.MacAddress].Rssi = tag.Rssi; 
         } 
         else 
         { 
          displayableTags.Add(tag.MacAddress, tag); 
         } 
        } 
        else 
        { 
         log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag is not displayable ({0})", tag); 

         RemoveTag(tag, displayableTags); 
        } 
       } 

       LogKeysAndValues(displayableTags); 

       PublishCurrentDisplayableTags(displayableTags); 
      } 
      else 
      { 
       log.WarnFormat(CultureInfo.InvariantCulture, "Received message of unknown type {0}.", message.GetType()); 
      } 
     } 
     catch (OperationCanceledException) 
     { 
      break; 
     } 
    } 
} 

private void PublishCurrentDisplayableTags(IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    eventAggregator.Publish(new CurrentDisplayableTags(displayableTags.Values.Distinct().ToList())); 
} 

private void RemoveTag(TagData tag, IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    displayableTags.Remove(tag.MacAddress); 

    // Now try to remove any duplicates and if there are then log it out 
    bool removalWasSuccesful = displayableTags.Remove(tag.MacAddress); 

    while (removalWasSuccesful) 
    { 
     log.WarnFormat(CultureInfo.InvariantCulture, "Duplicate tag removed from dictionary: {0}", tag.MacAddress); 
     removalWasSuccesful = displayableTags.Remove(tag.MacAddress); 
    } 
} 

private void LogKeysAndValues(IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    log.TraceFormat(CultureInfo.InvariantCulture, "Keys"); 
    foreach (var physicalAddress in displayableTags.Keys) 
    { 
     log.TraceFormat(CultureInfo.InvariantCulture, "Address: {0}", physicalAddress); 
    } 

    log.TraceFormat(CultureInfo.InvariantCulture, "Values"); 
    foreach (TagData physicalAddress in displayableTags.Values) 
    { 
     log.TraceFormat(CultureInfo.InvariantCulture, "Address: {0} Name: {1}", physicalAddress.MacAddress, physicalAddress.Name); 
    } 
} 

Và thông điệp quá trình được sử dụng như sau:

Thread processingThread = new Thread(ProcessMessages); 

GetFromTagReports Mã

public IEnumerable<TagData> GetFromTagReports(IEnumerable<TagReport> tagReports) 
{ 
    foreach (var tagReport in tagReports) 
    { 
     TagData tagData = GetFromMacAddress(tagReport.MacAddress); 
     tagData.Rssi = tagReport.ReceivedSignalStrength; 
     tagData.ExciterId = tagReport.ExciterId; 
     tagData.MacAddress = tagReport.MacAddress; 
     tagData.Arrived = tagReport.TimeStamp; 

     yield return tagData; 
    } 
} 

public TagData GetFromMacAddress(PhysicalAddress macAddress) 
{ 
    TagId physicalAddressToTagId = TagId.Parse(macAddress); 

    var personEntity = personFinder.ByTagId(physicalAddressToTagId); 

    if (personEntity.Person != null && !(personEntity.Person is UnknownPerson)) 
    { 
     return new TagData(TagType.Person, personEntity.Person.Name); 
    } 

    var tagEntity = tagFinder.ByTagId(physicalAddressToTagId); 

    if (TagId.Invalid == tagEntity.Tag) 
    { 
     return TagData.CreateUnknownTagData(macAddress); 
    } 

    var equipmentEntity = equipmentFinder.ById(tagEntity.MineSuiteId); 

    if (equipmentEntity.Equipment != null && !(equipmentEntity.Equipment is UnknownEquipment)) 
    { 
     return new TagData(TagType.Vehicle, equipmentEntity.Equipment.Name); 
    } 

    return TagData.CreateUnknownTagData(macAddress); 
} 

đâu Physical Address được tạo ra

var physicalAddressBytes = new byte[6]; 
ByteWriter.WriteBytesToBuffer(physicalAddressBytes, 0, protocolDataUnit.Payload, 4, 6); 

var args = new TagReport 
{ 
    Version = protocolDataUnit.Version, 
    MacAddress = new PhysicalAddress(physicalAddressBytes), 
    BatteryStatus = protocolDataUnit.Payload[10], 
    ReceivedSignalStrength = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(protocolDataUnit.Payload, 12)), 
    ExciterId = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(protocolDataUnit.Payload, 14)) 
}; 

public static void WriteBytesToBuffer(byte[] oldValues, int oldValuesStartindex, byte[] newValues, int newValuesStartindex, int max) 
{ 
    var loopmax = (max > newValues.Length || max < 0) ? newValues.Length : max; 

    for (int i = 0; i < loopmax; ++i) 
    { 
     oldValues[oldValuesStartindex + i] = newValues[newValuesStartindex + i]; 
    } 
} 

Lưu ý những điều sau:

  • Mỗi 'Tag' trong messages.Tags chứa một 'mới' PhysicalAddress.
  • Mỗi Thẻ được trả về cũng là 'mới'.
  • Các phương pháp 'tagRules' không sửa đổi được truyền vào 'thẻ' theo bất kỳ cách nào.
  • Thử nghiệm riêng lẻ với việc cố gắng đặt hai phiên bản của PhysicalAddress (được tạo từ cùng một byte) vào một Từ điển sẽ ném ngoại lệ 'KeyAlreadyExists'.
  • Tôi cũng đã thử TryGetValue và nó tạo ra kết quả tương tự.

đầu ra Log nơi mà mọi thứ vẫn ổn:

2013-04-26 18:28:34,347 [8] DEBUG ClassName - Detected tag (000CCC756081) with Exciter Id (0) 
2013-04-26 18:28:34,347 [8] DEBUG ClassName - Detected tag is displayable (Unknown: ?56081) 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Keys 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755898 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755A27 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755B47 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Values 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755898 Name: Scotty McTester 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755A27 Name: JDTest1 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755B47 Name: 33 1 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Current tags: Scotty McTester, ?56081, JDTest1, 33 1 

Log ra nơi mà chúng tôi có được một khóa trùng lặp:

2013-04-26 18:28:35,608 [8] DEBUG ClassName - Detected tag (000CCC756081) with Exciter Id (0) 
2013-04-26 18:28:35,608 [8] DEBUG ClassName - Detected tag is displayable (Unknown: ?56081) 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Keys 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Address: 000CCC755898 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755A27 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755B47 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Values 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755898 Name: Scotty McTester 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC755A27 Name: JDTest1 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC755B47 Name: 33 1 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Current tags: Scotty McTester, ?56081, JDTest1, 33 1, ?56081 

Chú ý rằng tất cả mọi thứ đang xảy ra trên một sợi đơn (xem [8 ]) vì vậy không có cơ hội của từ điển đã được đồng thời sửa đổi. Các trích đoạn từ cùng một bản ghi và cùng một cá thể tiến trình. Cũng lưu ý rằng trong tập hợp nhật ký thứ hai, chúng tôi kết thúc bằng hai phím giống nhau!

Điều tôi đang xem xét: Tôi đã thay đổi PhysicalAddress thành chuỗi để xem liệu tôi có thể loại bỏ điều đó khỏi danh sách nghi phạm hay không.

Câu hỏi của tôi là:

  • Có một vấn đề mà tôi không nhìn thấy trong đoạn code trên?
  • Có vấn đề gì với các phương pháp bình đẳng trên PhysicalAddress không? (Đó chỉ là lỗi ngay bây giờ?)
  • Có vấn đề gì với Từ điển không?
+0

Bạn có thể nhận thấy rằng hoạt động không hoạt động không xảy ra cùng một lúc. Điều này có thể là một đối số cho thread có vấn đề. Làm thế nào bạn có thể chắc chắn rằng 'displayableTags' không phải là một đối tượng được chia sẻ? Đây có phải là biến cục bộ không? Một tài sản? Hơn nữa, sử dụng 'TryGetValue' thay vì' ContainsKey'. –

+0

Tôi có thể chắc chắn vì 'displayableTags' là một biến được tạo cục bộ được tạo trong phương thức được gọi bởi hàm tạo Thread. Tôi đã thử TryGetValue và nó đã làm điều tương tự (tôi sẽ thêm điều đó vào câu hỏi). Ngoài ra, từ tài liệu MSDN trên TryGetValue: _Phương pháp này kết hợp chức năng của phương thức ContainsKey và thuộc tính Item._ – JohnDRoach

+0

Bạn có thể đăng mã trong một khối không? Vấn đề có thể là bạn trong hàm Log của bạn, chúng ta có thể thấy điều đó không? –

Trả lời

9

Từ điển mong đợi đối tượng bất biến là chìa khóa, với việc thực hiện GetHashCode/Equals ổn định. Điều này có nghĩa là sau khi đối tượng được đặt vào từ điển, giá trị trả về bởi GetHashCode nên không thay đổi và bất kỳ thay đổi nào được thực hiện đối tượng này sẽ không ảnh hưởng đến phương thức Equals.

Mặc dù lớp PhysicalAddress được thiết kế không thay đổi, nhưng nó vẫn chứa một vài điểm mở rộng, nơi tính bất biến của nó không hoàn thiện.

Thứ nhất, nó có thể được thay đổi thông qua mảng byte đầu vào, mà không sao chép nhưng thông qua tham khảo, như thế này:

var data = new byte[] { 1,2,3 }; 
var mac = new PhysicalAddress(data); 
data[0] = 0; 

Thứ hai, PhysicalAddress không phải là một lớp niêm phong, và có thể được thay đổi bằng cách lấy thực hiện thông qua ghi đè phương pháp Constructor/GetHashCode/Equals. Nhưng trường hợp sử dụng này trông giống như một hack, vì vậy chúng tôi sẽ bỏ qua nó, cũng như sửa đổi thông qua sự phản ánh.

Tình huống của bạn chỉ có thể đạt được bằng cách đặt đối tượng PhysicalAddress vào từ điển, và sau đó sửa đổi mảng byte nguồn của nó, sau đó gói nó vào thể hiện PhysicalAddress mới.

May mắn thay, triển khai GetHashCode chỉ tính một lần, và nếu cùng một trường hợp được sửa đổi, nó vẫn được đặt vào cùng một từ điển, và được đặt lại bằng Equals.

Nhưng, nếu mảng nguồn byte được truyền vào một thể hiện của PhysicalAddress, nơi băm được chưa tính - băm được tính toán lại cho byte [] giá trị mới, xô mới có vị trí, và trùng lặp được đưa vào từ điển. Trong một số ít trường hợp, cùng một nhóm có thể được đặt từ băm mới và một lần nữa, không có bản sao nào được chèn vào.

Dưới đây là đoạn code mà tái tạo các vấn đề:

using System; 
using System.Collections.Generic; 
using System.Net.NetworkInformation; 

class App 
{ 
    static void Main() 
    { 
    var data = new byte[] { 1,2,3,4 }; 
    var mac1 = new PhysicalAddress(data); 
    var mac2 = new PhysicalAddress(data); 
    var dictionary = new Dictionary<PhysicalAddress,string>(); 
    dictionary[mac1] = "A"; 
    Console.WriteLine("Has mac1:" + dictionary.ContainsKey(mac1)); 
    //Console.WriteLine("Has mac2:" + dictionary.ContainsKey(mac2)); 
    data[0] = 0; 
    Console.WriteLine("After modification"); 
    Console.WriteLine("Has mac1:" + dictionary.ContainsKey(mac1)); 
    Console.WriteLine("Has mac2:" + dictionary.ContainsKey(mac2)); 

    dictionary[mac2] = "B"; 
    foreach (var kvp in dictionary) 
     Console.WriteLine(kvp.Key + "=" + kvp.Value); 
    } 
} 

Lưu ý dòng nhận xét - nếu chúng ta sẽ bỏ ghi chú nó, "containsKey" phương pháp sẽ precompute băm cho MAC2, và nó sẽ giống nhau ngay cả sau khi sửa đổi.

Vì vậy, đề xuất của tôi là xác định vị trí đoạn mã tạo cá thể PhysicalAddress và tạo bản sao mảng byte mới cho mỗi lệnh gọi hàm tạo.

+0

Cảm ơn bạn vì câu trả lời được xây dựng tốt :) Thật không may, chúng tôi đã tạo một mảng byte mới với mỗi lệnh gọi hàm tạo. Xem mã gần đây được thêm vào câu hỏi. Thuộc tính MacAddress trên TagReport không bao giờ được gán sau đó và chỉ được sử dụng. Ngoài ra trường hợp được tạo ra ở đó cuối cùng đã làm cho nó là cách để gọi GetTagReports. – JohnDRoach

+0

Điều gì xảy ra với physicalAddressBytes sau khi TagReport được xây dựng? Nó được tái sử dụng ở đâu đó? Kích thước trung bình của từ điển là gì? Mức độ thường xuyên được sửa đổi? – Alexander

+0

Vài ý tưởng khác - hãy thử kiểm tra bộ nhớ của máy chủ nơi bạn có thể tạo lại vấn đề này. Đặt tất cả các phương pháp truy cập từ điển của bạn vào khóa(), để đảm bảo không có vấn đề đa luồng. – Alexander

Các vấn đề liên quan