2008-10-01 31 views
8

Tôi có một Dictionary<string, someobject>.Sử dụng khóa trên khóa của Từ điển <string, object>

EDIT: Nó đã được chỉ ra cho tôi, rằng ví dụ của tôi là xấu. Toàn bộ ý định của tôi không cập nhật các tham chiếu trong một vòng lặp nhưng để cập nhật các giá trị khác nhau dựa trên các chủ đề khác nhau cần phải cập nhật/lấy dữ liệu. Tôi đã thay đổi vòng lặp thành một phương thức.

Tôi cần cập nhật các mục trong từ điển của mình - một khóa cùng một lúc và tôi đã tự hỏi liệu có bất kỳ sự cố nào khi sử dụng khóa trên giá trị .key của đối tượng Từ điển của tôi không?

private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>(); 

//Pseudo-code 

public static void UpdateValue(string key) 
{ 
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key]; 
    lock (keyValuePair.Key) 
    { 
     keyValuePair.Value = SomeMeanMethod(); 
    } 
} 

Điều đó có thể xảy ra tại tòa hay không? Tôi chỉ muốn mỗi giá trị trong từ điển được khóa độc lập để khóa (và cập nhật) một giá trị không khóa những giá trị khác. Ngoài ra tôi biết rằng khóa sẽ được giữ trong một thời gian dài - nhưng dữ liệu sẽ không hợp lệ cho đến khi được cập nhật đầy đủ.

+0

Không phải là câu trả lời, nhưng bạn biết rằng nếu bạn đã có cặp, bạn có thể sử dụng pair.Value thay vì gọi operator [] một lần nữa trên từ điển? – OregonGhost

+0

Thực ra tôi đã thử, nhưng Giá trị là chỉ đọc hoặc vì vậy IDE của tôi đã nói với tôi? –

+0

bạn không thể sửa đổi các yếu tố foreach – sieben

Trả lời

11

Khóa trên một đối tượng có thể truy cập bên ngoài mã khóa là một rủi ro lớn. Nếu bất kỳ mã nào khác (bất cứ nơi nào) bao giờ khóa đối tượng mà bạn có thể ở trong một số deadlocks mà khó gỡ lỗi. Cũng lưu ý rằng bạn khóa đối tượng , không phải tham chiếu, vì vậy nếu tôi cung cấp cho bạn từ điển, tôi vẫn có thể giữ tham chiếu đến các phím và khóa trên chúng - khiến chúng tôi khóa trên cùng một đối tượng.

Nếu bạn hoàn toàn đóng gói từ điển, và tạo ra các phím mình (họ không bao giờ được thông qua vào, sau đó bạn có thể an toàn

Tuy nhiên, hãy cố gắng dính vào một quy tắc -. Hạn chế tầm nhìn của các đối tượng bạn khóa trên để mã khóa riêng của mình bất cứ khi nào có thể

Đó là lý do tại sao bạn thấy điều này:.

public class Something 
{ 
    private readonly object lockObj = new object(); 

    public SomethingReentrant() 
    { 
    lock(lockObj) // Line A 
    { 
     // ... 
    } 
    } 
} 

chứ không phải nhìn thấy dòng A ở trên thay thế bằng

lock(this) 

Bằng cách đó, một đối tượng riêng biệt bị khóa và mức độ hiển thị bị giới hạn.

Chỉnh sửaJon Skeet đã quan sát chính xác khóa lockObj ở trên phải chỉ đọc.

+6

Tôi muốn thêm rằng lockObj hầu như luôn luôn được khai báo chỉ đọc. –

8

Không, điều này sẽ không hoạt động.

Lý do là string interning. Điều này có nghĩa là:

string a = "Something"; 
string b = "Something"; 

đều là cùng một đối tượng! Do đó, bạn không bao giờ nên khóa chuỗi bởi vì nếu một phần khác của chương trình (ví dụ: một thể hiện của cùng một đối tượng này) cũng muốn khóa trên cùng một chuỗi, bạn có thể vô tình tạo ra tranh chấp khóa mà không cần đến nó; thậm chí có thể là bế tắc.

Hãy thoải mái thực hiện điều này bằng cách không có dây. Để rõ ràng nhất, tôi tạo thói quen cá nhân để luôn tạo ra một đối tượng khóa riêng biệt:

class Something 
{ 
    bool threadSafeBool = true; 
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool 
} 

Tôi khuyên bạn nên làm như vậy. Tạo một từ điển với các đối tượng khóa cho mỗi ô ma trận. Sau đó, khóa các vật này khi cần.

PS. Thay đổi bộ sưu tập bạn đang lặp lại không được coi là rất hay. Nó thậm chí sẽ ném một ngoại lệ với hầu hết các loại bộ sưu tập. Hãy cố gắng tái cấu trúc này - ví dụ: lặp qua danh sách các khóa, nếu nó luôn luôn là hằng số, không phải là các cặp.

+0

Bạn cũng có thể vô hiệu hóa việc thực hiện chuỗi - mà tôi tin tất cả các khung công tác .NET. –

+1

Phím "" cho một từ điển có thể là bất kỳ đối tượng nào, không chỉ là một chuỗi! Điều này sẽ không áp dụng nếu khóa là một số nguyên! –

+1

Điều quan trọng cần lưu ý là chỉ có các chuỗi ký tự được ẩn hoàn toàn. Người ta phải sử dụng string.Intern() cho các chuỗi khác. Tôi hoàn toàn đồng ý với quan điểm của bạn rằng các chuỗi không an toàn cho loại điều này bởi vì bất kỳ chuỗi đã cho có thể được thực tập, có thể gây ra các vấn đề tinh tế trong khóa. –

0

Trong ví dụ của bạn, bạn không thể làm những gì bạn muốn làm!

Bạn sẽ nhận được System.InvalidOperationException với thông báo Bộ sưu tập đã được sửa đổi; hoạt động điều tra có thể không thực hiện được.

Dưới đây là một ví dụ để chứng minh:

using System.Collections.Generic; 
using System; 

public class Test 
{ 
    private Int32 age = 42; 

    static public void Main() 
    { 
     (new Test()).TestMethod(); 
    } 

    public void TestMethod() 
    { 
     Dictionary<Int32, string> myDict = new Dictionary<Int32, string>(); 

     myDict[age] = age.ToString(); 

     foreach(KeyValuePair<Int32, string> pair in myDict) 
     { 
      Console.WriteLine("{0} : {1}", pair.Key, pair.Value); 
      ++age; 
      Console.WriteLine("{0} : {1}", pair.Key, pair.Value); 
      myDict[pair.Key] = "new"; 
      Console.WriteLine("Changed!"); 
     } 
    } 
} 

Kết quả sẽ là:

42 : 42 
42 : 42 

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute. 
    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) 
    at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext() 
    at Test.TestMethod() 
    at Test.Main() 
+0

Tôi không muốn khóa toàn bộ từ điển - chỉ có keypairvalue - vì vậy không có hai quá trình nào sẽ được cập nhật cùng một giá trị trong cặp giá trị tại một thời điểm. –

+0

Tôi không chaning chìa khóa - chỉ có giá trị, vì vậy nó không phải là một vấn đề –

0

tôi có thể thấy một vài vấn đề tiềm năng đó:

  1. chuỗi có thể được chia sẻ, vì vậy bạn không nhất thiết phải biết ai khác có thể đang khóa trên đối tượng chính đó cho những gì khác reaso n
  2. chuỗi có thể không được chia sẻ: bạn có thể đang khóa trên một chuỗi khóa có giá trị "Key1" và một số đoạn mã khác có thể có đối tượng chuỗi khác cũng chứa các ký tự "Key1". Đối với từ điển, chúng là cùng một khóa nhưng theo như khóa thì chúng là các đối tượng khác nhau.
  3. khóa đó sẽ không ngăn chặn thay đổi giá trị các đối tượng chính mình, tức là matrixElements[someKey].ChangeAllYourContents()
3

Lưu ý: Tôi giả định ngoại lệ khi sửa đổi bộ sưu tập trong lần lặp đã được cố định

điển không phải là thread-safe bộ sưu tập , có nghĩa là không phải an toàn để sửa đổi và đọc bộ sưu tập từ các chủ đề khác nhau mà không cần đồng bộ hóa bên ngoài. Hashtable là (được?) Thread-an toàn cho một kịch bản nhiều người viết, nhiều người đọc, nhưng từ điển có cấu trúc dữ liệu nội bộ khác nhau và không kế thừa bảo đảm này.

Điều này có nghĩa là bạn không thể sửa đổi từ điển trong khi truy cập từ điển để đọc hoặc ghi từ chủ đề khác, nó chỉ có thể phá vỡ cấu trúc dữ liệu nội bộ. Khóa trên khóa không bảo vệ cấu trúc dữ liệu nội bộ, bởi vì trong khi bạn sửa đổi chính khóa đó, ai đó có thể đọc khóa khác nhau của từ điển của bạn trong một chuỗi khác. Ngay cả khi bạn có thể đảm bảo rằng tất cả các phím của bạn đều là các đối tượng giống nhau (như đã nói về chuỗi ký tự), điều này không mang lại cho bạn sự an toàn. Ví dụ:

  1. Bạn khóa phím và bắt đầu thay đổi từ điển
  2. chủ đề khác cố gắng để có được giá trị cho khóa đó xảy ra rơi vào xô giống như bị khóa một. Điều này không chỉ khi hashcodes của hai đối tượng là như nhau, nhưng thường xuyên hơn khi hashcode% tableSize là như nhau.
  3. Cả hai chủ đề được truy cập vào xô nhau (liên kết danh sách các phím với cùng hashcode giá trị% tableSize)

Nếu không có chìa khóa như trong từ điển, chủ đề đầu tiên sẽ bắt đầu sửa đổi danh sách, và các chủ đề thứ hai sẽ có khả năng đọc trạng thái không đầy đủ.

Nếu khóa như vậy đã tồn tại, chi tiết triển khai từ điển vẫn có thể sửa đổi cấu trúc dữ liệu, ví dụ di chuyển các khóa đã truy cập gần đây vào đầu danh sách để truy xuất nhanh hơn. Bạn không thể dựa vào chi tiết triển khai.

Có nhiều trường hợp như vậy, khi bạn sẽ có từ điển bị hỏng. Vì vậy, bạn phải có đối tượng đồng bộ hóa bên ngoài (hoặc sử dụng từ điển chính nó, nếu nó không được tiếp xúc với công chúng) và khóa trên nó trong toàn bộ hoạt động. Nếu bạn cần khóa chi tiết hơn khi hoạt động có thể mất một thời gian dài, bạn có thể sao chép các khóa bạn cần cập nhật, lặp lại, khóa toàn bộ từ điển trong khi cập nhật khóa đơn (đừng quên xác minh khóa vẫn còn ở đó) và giải phóng nó để cho các luồng khác chạy.

2

Nếu tôi không nhầm, mục đích ban đầu là để khóa vào một yếu tố duy nhất, chứ không phải khóa toàn bộ từ điển (như khóa bàn cấp vs khóa mức hàng trong một DB)

bạn có thể' t khóa trên chìa khóa của từ điển như nhiều ở đây giải thích.

Điều bạn có thể làm là giữ từ điển nội bộ của các đối tượng khóa, tương ứng với từ điển thực tế. Vì vậy, khi bạn muốn viết thư cho YourDictionary [Key1], trước tiên bạn sẽ khóa trên InternalLocksDictionary [Key1] - vì vậy chỉ một luồng duy nhất sẽ ghi vào YourDictionary.

ví dụ (không quá sạch) có thể là found here.

0

Chỉ cần đi qua này và nghĩ rằng id cổ phiếu một số mã tôi đã viết một vài năm trước đây, nơi tôi cần đến một cuốn từ điển trên cơ sở chủ chốt

using (var lockObject = new Lock(hashedCacheID)) 
{ 
    var lockedKey = lockObject.GetLock(); 
    //now do something with the dictionary 
} 

lớp khóa

class Lock : IDisposable 
    { 
     private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>(); 

     private static readonly object CritialLock = new object(); 

     private readonly string _key; 
     private bool _isLocked; 

     public Lock(string key) 
     { 
      _key = key; 

      lock (CritialLock) 
      { 
       //if the dictionary doesnt contain the key add it 
       if (!Lockedkeys.ContainsKey(key)) 
       { 
        Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references 
       } 
      } 
     } 

     public string GetLock() 
     { 
      var key = Lockedkeys[_key]; 

      if (!_isLocked) 
      { 
       Monitor.Enter(key); 
      } 
      _isLocked = true; 

      return key; 
     } 

     public void Dispose() 
     { 
      var key = Lockedkeys[_key]; 

      if (_isLocked) 
      { 
       Monitor.Exit(key); 
      } 
      _isLocked = false; 
     } 
    } 
Các vấn đề liên quan