2017-06-07 22 views
6

Tôi đang ở trong tình huống này có một từ điển lớn được cập nhật ngẫu nhiên bởi một luồng ở tần số khá cao, và có một luồng khác cố gắng chụp nhanh từ điển để lưu làm lịch sử. Tôi hiện đang sử dụng một cái gì đó như thế này:Trong C#, làm thế nào để lặp lại IEnumerable trong môi trường đa luồng

Dictionary<string, object> dict = new Dictionary<string, object>(); 
var items = dict.Values.ToList(); 

này hoạt động tốt đối với hầu hết các trường hợp, ngoại trừ nó thỉnh thoảng ném:

System.InvalidOperationException: Bộ sưu tập đã được sửa đổi; liệt kê hoạt động có thể không thực thi.

Tôi hiểu tại sao điều này xảy ra, nhưng tôi không biết tôi có thể làm gì để tránh lỗi sửa đổi bộ sưu tập.

Cách tiếp cận tốt nhất để lặp lại bộ sưu tập đó là gì?

Tôi cũng đã thử ConcurrentDictionary nhưng không may mắn. Tại sao? Chủ đề ConcurrentDictionary chỉ an toàn ở cấp mục?

+0

Bạn nên sử dụng 'khóa'. – Rob

+0

Trong trường hợp của tôi, nhiều giải pháp được đề xuất ở đây cũng sẽ hoạt động, nhưng giải pháp được chấp nhận phù hợp nhất với tôi. Xin lỗi vì trả lời muộn. –

Trả lời

0

Theo the docs bạn sẽ có thể sử dụng phương pháp ConcurrentDictionary GetEnumerator() để nhận trình lặp lặp an toàn chỉ.

Bộ đếm được trả về từ điển là an toàn để sử dụng đồng thời với lần đọc và ghi vào từ điển, tuy nhiên nó không đại diện cho ảnh chụp nhanh trong từ điển. Các nội dung tiếp xúc thông qua điều tra viên có thể chứa các sửa đổi được thực hiện cho từ điển sau khi GetEnumerator được gọi.

Vì bạn đang xử lý các chủ đề đồng thời, không có gì đáng ngạc nhiên khi có một số sự cân bằng, nhưng tôi mong đợi phương pháp này chặn ít hơn phương pháp tiếp cận vũ lực được đưa ra trong các câu trả lời khác.Điều này sẽ không làm việc nếu bạn đã cố gắng:

var items = concurrentDict.Items.ToList(); 

nhưng nó là nghĩa vụ phải làm việc cho

var items = concurrentDict.GetEnumerator(); 

hoặc bạn chỉ có thể tham khảo các iterator trực tiếp:

foreach(var item in concurrentDict) 
{ 
    valueList.Add(item.Value); 
} 
+0

Tôi đã kiểm tra mã nguồn ToList, nó sử dụng bản sao mảng thay vì lặp lại mặc dù điều tra viên, đó có thể là lý do tại sao ToList đôi khi sẽ không hoạt động. –

0

Bạn có thể sử dụng màn hình với từ khóa lock để đảm bảo rằng chỉ đọc hoặc chỉ viết được thực hiện tại thời điểm này.

public class SnapshotDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> 
{ 
    private readonly Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>(); 
    private readonly object _lock = new object(); 

    public void Add(TKey key, TValue value) 
    { 
     lock (_lock) 
     { 
      _dictionary.Add(key, value); 
     } 
    } 

    // TODO: Other necessary IDictionary methods 

    public Dictionary<TKey, TValue> GetSnaphot() 
    { 
     lock (_lock) 
     { 
      return new Dictionary<TKey, TValue>(_dictionary); 
     } 
    } 

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 
    { 
     return GetSnaphot().GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

GetSnapshot phương thức trả về ảnh chụp nhanh từ điển của bạn.
Tôi cũng đã ghi đè lên GetEnumerator để nó tạo ảnh chụp nhanh và sau đó trả về điều tra của ảnh chụp nhanh.

Vì vậy, điều này sẽ làm việc vì sẽ được thực hiện trên một bản chụp:

var items = snapshotDictionary.GetSnapshot().Values.ToList(); 

// or 

foreach (var item in snapshotDictionary) 
{ 
    // ... 
} 

Tuy nhiên, phương pháp này không cho phép xử lý đa luồng bằng văn bản.

1

Một ImmutableDictionary có thể phù hợp với bạn, vì nó hỗ trợ khả năng mở rộng đa luồng có thể mở rộng như là một phần của bộ tính năng cơ bản của nó.

// initialize. 
ImmutableDictionary<string, int> dict = ImmutableDictionary.Create<string,int>(); 

// create a new dictionary with "foo" key added. 
ImmutableDictionary<string, int> newdict = dict.Add("foo", 0); 

// replace dict, thread-safe, with a new dictionary with "bar" added. 
// note this is using dict, not newdict, so there is no "foo" in it. 
ImmutableInterlocked.TryAdd(ref dict, "bar", 1); 

// take a snapshot, thread-safe. 
ImmutableDictionary<string,int> snapshot = dict; 

Tính chất không thay đổi có nghĩa là từ điển không bao giờ có thể thay đổi - bạn chỉ có thể thêm giá trị bằng cách tạo từ điển mới. Và vì thuộc tính này, bạn có một "ảnh chụp nhanh" của nó bằng cách đơn giản giữ một tham chiếu xung quanh từ điểm bạn muốn chụp nhanh.

Nó được tối ưu hóa dưới mui xe để có hiệu quả, không sao chép toàn bộ điều cho mọi hoạt động. Điều đó nói rằng, cho các hoạt động khác nó không phải là hiệu quả như ConcurrentDictionary, nhưng đó là tất cả một sự cân bằng trong những gì bạn muốn. Ví dụ: ConcurrentDictionary có thể được liệt kê đồng thời nhưng không thể liệt kê ảnh chụp nhanh của nó.

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