2011-11-16 33 views
8

Chúng tôi thấy ngoại lệ này xảy ra trong khối mã sau trong ngữ cảnh ASP.NET đang chạy trên máy chủ IIS 7.Ngoại lệ khi thêm mục nhập từ điển

1) Exception Information 
********************************************* 
Exception Type: System.Exception 
Message: Exception Caught in Application_Error event 
Error in: InitializationStatus.aspx 
Error Message:An item with the same key has already been added. 
Stack Trace: at 
System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 
at CredentialsSession.GetXmlSerializer(Type serializerType) 

Đây là mã mà các ngoại lệ được xảy ra trong:

[Serializable()] 
public class CredentialsSession 
{ 
    private static Dictionary<string, System.Xml.Serialization.XmlSerializer> localSerializers = new Dictionary<string, XmlSerializer>(); 

    private System.Xml.Serialization.XmlSerializer GetXmlSerializer(Type serializerType) 
    { 
     string sessionObjectName = serializerType.ToString() + ".Serializer"; 

     if (Monitor.TryEnter(this)) 
     { 
      try 
      { 
       if (!localSerializers.ContainsKey(sessionObjectName)) 
       { 
        localSerializers.Add(sessionObjectName, CreateSerializer(serializerType)); 
       } 
      } 
      finally 
      { 
       Monitor.Exit(this); 
      } 
     } 
     return localSerializers[sessionObjectName]; 
    } 

    private System.Xml.Serialization.XmlSerializer CreateSerializer(Type serializerType) 
    { 
     XmlAttributes xmlAttributes = GetXmlOverrides(); 

     XmlAttributeOverrides xmlOverrides = new XmlAttributeOverrides(); 
     xmlOverrides.Add(typeof(ElementBase), "Elements", xmlAttributes); 

     System.Xml.Serialization.XmlSerializer serializer = 
      new System.Xml.Serialization.XmlSerializer(serializerType, xmlOverrides); 

     return serializer; 
    } 
} 

Các Monitor.TryEnter nên ngăn ngừa nhiều chủ đề từ khi vào khối cùng một lúc, và mã được kiểm tra từ điển để xác minh rằng nó không chứa khóa đang được thêm vào.

Bất kỳ ý tưởng nào về điều này có thể xảy ra như thế nào?

+0

+1, cho câu hỏi giải thích cách tìm từ khóa trùng lặp trong Từ điển. –

Trả lời

5

Mã của bạn không an toàn chỉ.

  1. Bạn đang khóa trên this, một trường hợp CredentialsSession, nhưng việc tiếp cận một cuốn từ điển tĩnh mà có thể được chia sẻ bởi nhiều CredentialsSession trường. Điều này giải thích tại sao bạn nhận được lỗi - hai trường hợp khác nhau CredentialsSession đang cố ghi vào từ điển đồng thời.

  2. Ngay cả khi bạn thay đổi điều này thành khóa trên trường tĩnh như được đề xuất trong câu trả lời của @ sll, bạn không an toàn chỉ vì bạn không khóa khi đọc từ điển. Bạn cần một số ReaderWriterLock hoặc ReaderWriterLockSlim để cho phép nhiều người đọc và một người viết duy nhất có hiệu quả.

    Vì vậy, có lẽ bạn nên sử dụng từ điển an toàn cho chủ đề. ConcurrentDictionary như những người khác đã nói nếu bạn đang sử dụng .NET 4.0. Nếu không, bạn nên thực hiện của riêng bạn, hoặc sử dụng một thực hiện hiện có như http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx.

Nhận xét của bạn đề nghị bạn muốn tránh gọi CreateSerializer cho cùng một loại nhiều lần. Tôi không biết tại sao, bởi vì lợi ích hiệu suất có thể là không đáng kể, vì tranh chấp có thể là hiếm và không thể vượt quá một lần cho mỗi loại trong suốt thời gian tồn tại của ứng dụng.

Nhưng nếu bạn thực sự muốn điều này, bạn có thể làm điều đó như sau:

var value; 
if (!dictionary.TryGetValue(key, out value)) 
{ 
    lock(dictionary) 
    { 
     if(!dictionary.TryGetValue(key, out value)) 
     { 
      value = CreateSerializer(...); 
      dictionary[key] = value; 
     } 
    } 
} 

Từ bình luận:

nếu tôi thực hiện điều này với ConcurrentDictionary và chỉ cần gọi TryAdd (sessionObjectName, CreateSerializer (serializerType)) mỗi lần.

Câu trả lời là không gọi TryAdd mỗi lần - trước tiên hãy kiểm tra xem nó có trong từ điển hay không, sau đó thêm nếu không. Cách thay thế tốt hơn có thể là sử dụng the GetOrAdd overload có tham số Func.

+0

Tôi đã không nhận thấy từ điển là tĩnh! Điều đó giải thích rất nhiều, cảm ơn! Tôi đang thực hiện lại điều này với ConcurrentDictionary. – Avalanchis

+0

Giả sử GetXmlSerializer được gọi thường xuyên để lấy giá trị từ điển, nếu tôi thực hiện điều này với ConcurrentDictionary và chỉ cần gọi TryAdd (sessionObjectName, CreateSerializer (serializerType)) mỗi lần, sau đó CreateSerializer sẽ được gọi mỗi khi GetXmlSerializer được gọi. Có vẻ như tôi muốn tránh điều này. Khi từ điển được điền cho mỗi giá trị khóa, không có lý do gì để gọi hàm CreateSerializer nữa.Chính xác? – Avalanchis

+2

+1 vì câu trả lời này mô tả đầy đủ vấn đề hiện tại và đề xuất cách giải quyết nó cho các trường hợp khác nhau – sll

5

Thử khóa trên localSerializers thay vì this. BTW, tại sao bạn đang sử dụng Màn hình một cách rõ ràng? Chỉ có một lý do tôi thấy là để cung cấp lock timeout mà rõ ràng là bạn không sử dụng, vì vậy sử dụng chỉ đơn giản lock() statement thay vì điều này sẽ tạo ra try/cuối cùng cũng như:

lock (localSerializers) 
{ 
    if (!localSerializers.ContainsKey(sessionObjectName))     
    {      
     localSerializers.Add(
      sessionObjectName, 
      CreateSerializer(serializerType));     
    } 
} 

EDIT: Vì bạn đã không được chỉ rõ trong các thẻ mà bạn đang sử dụng .NET 4 tôi sẽ đề nghị sử dụng ConcurrentDictionary<TKey, TValue>


Monitor.Enter() Method:

Sử dụng C# try… finally block (Thử… Cuối cùng trong Visual Basic) để đảm bảo mà bạn nhả màn hình hoặc sử dụng lệnh C# lock (SyncLock statement trong Visual Basic), bao gồm các phương thức Enter và Exit trong một thử ... cuối cùng chặn

+0

Cảm ơn bạn đã phản hồi! Không phải ban đầu mã của tôi, vì vậy tôi không thể cung cấp một câu trả lời cho lý do tại sao nó đang sử dụng Màn hình một cách rõ ràng. Nó làm cho cảm giác hoàn hảo để khóa tài nguyên đang được bảo vệ hơn là điều này. Tôi sẽ cố gắng này, cảm ơn! – Avalanchis

+0

@Avalanchis: xem phần EDIT của câu trả lời cập nhật, tôi cũng đã thêm thẻ .NET 4 cho câu hỏi của bạn, điều này khá quan trọng – sll

+0

Tôi thích ý tưởng sử dụng ConcurrentDictionary, nhưng tôi lo ngại về các cuộc gọi không cần thiết đến CreateSerializer. Dường như tôi vẫn cần gọi ContainsKey để xem liệu khóa có tồn tại trước khi gọi TryAdd không nếu tôi muốn tránh gọi hàm CreateSerializer mỗi lần. Chính xác? – Avalanchis

1

Nếu bạn đang ở trên .NET Framework 4 hoặc mới hơn, tôi sẽ đề nghị bạn nên sử dụng một ConcurrentDictionary để thay thế. Phương pháp TryAdd giữ cho bạn an toàn từ loại kịch bản, mà không cần phải xả rác mã của bạn với ổ khóa:

localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType)) 

Nếu bạn đang lo lắng về CreateSerializer được gọi khi nó không cần thiết, thay vào đó bạn nên sử dụng AddOrUpdate:

localSerializers.AddOrUpdate(
    sessionObjectName, 
    key => CreateSerialzer(serializerType), 
    (key, value) => value); 

Điều này sẽ đảm bảo rằng phương thức này chỉ được gọi khi bạn cần tạo một giá trị mới (khi cần phải thêm vào từ điển). Nếu nó đã có mặt, mục nhập sẽ được "cập nhật" với giá trị đã tồn tại.

+0

Chúng tôi đang sử dụng .NET 4. Liệu nó vẫn có ý nghĩa để kiểm tra xem ConcurrentDictionary có chứa khóa được thêm vào đầu tiên không? Tôi lo ngại rằng TryAdd có thể không cần thiết gọi CreateSerializer nếu khóa đã tồn tại. – Avalanchis

+0

@Avalanchis: xem câu trả lời được cập nhật. –

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