2012-04-04 23 views
7

Tôi lặp thông qua một danh sách các yếu tố với foreach, như thế này:Sửa đổi danh sách từ thread khác trong khi lặp lại (C#)

foreach (Type name in aList) { 
    name.doSomething(); 
} 

Tuy nhiên, trong một thread khác tôi kêu gọi một cái gì đó giống như

aList.Remove(Element); 

Trong thời gian chạy, điều này gây ra một InvalidOperationException: Bộ sưu tập đã được sửa đổi; hoạt động điều tra có thể không thực hiện được. Cách tốt nhất để xử lý này là gì (tôi sẽ suy nghĩ nó khá đơn giản ngay cả ở chi phí hiệu suất)?

Cảm ơn!

Trả lời

7

Chủ đề A:

lock (aList) { 
    foreach (Type name in aList) { 
    name.doSomething(); 
    } 
} 

Thread B:

lock (aList) { 
    aList.Remove(Element); 
} 

ofcourse Điều này thực sự không tốt cho hiệu suất.

+0

Cảm ơn, hoạt động như một nét duyên dáng :) Hãy hy vọng tôi sẽ không gặp phải bất kỳ vấn đề gì về hiệu năng bằng cách sử dụng .. –

9

Cách tốt nhất để xử lý điều này (Tôi sẽ cho rằng nó khá đơn giản ngay cả ở chi phí hiệu suất)?

Về cơ bản: không cố gắng sửa đổi bộ sưu tập không an toàn với chủ đề từ nhiều chuỗi mà không khóa. Thực tế là bạn đang lặp lại chủ yếu là không liên quan ở đây - nó chỉ giúp bạn tìm thấy nó nhanh hơn. Nó sẽ không an toàn cho hai chủ đề để cả hai được gọi Remove cùng một lúc.

Hoặc sử dụng một bộ sưu tập thread-safe như ConcurrentBaghoặc đảm bảo rằng chỉ một thread làm bất cứ điều gì với bộ sưu tập tại một thời điểm.

+0

Bạn không nghĩ về điều đó, nhưng giờ bạn đã nói rằng điều đó là hợp lý. Cảm ơn, tôi có thể sẽ sử dụng điều đó sau, nhưng đối với trường hợp này chỉ hoạt động tốt. –

0

nếu bạn chỉ muốn tránh những ngoại lệ sử dụng

foreach (Type name in aList.ToArray()) 
{ name.doSomething(); } 

phải nhận thức được các doSomething() được thực hiện cũng trong trường hợp nguyên tố này được loại bỏ trong các chủ đề khác

+1

Thật không may 'ToArray' và' Remove' có thể chạy đua có thể dẫn đến một loại ngoại lệ khác. Đó là một suy nghĩ tốt mặc dù! Tham khảo [answer] của tôi (http://stackoverflow.com/a/10018399/158779) để xem cách làm cho nó hoạt động chính xác. –

+0

Oh ... và chào mừng bạn đến stackoverflow :) –

0

tôi không thể đặc biệt kể từ câu hỏi của bạn, nhưng (có vẻ như) bạn đang thực hiện một hành động trên mỗi mục và sau đó xóa nó. Bạn có thể muốn xem xét BlockingCollection<T>, có phương thức gọi số GetConsumingEnumerable() để xem liệu phương thức đó có phù hợp với bạn hay không. Đây là một mẫu nhỏ.

void SomeMethod() 
{ 
    BlockingCollection<int> col = new BlockingCollection<int>(); 

    Task.StartNew(() => { 

     for (int j = 0; j < 50; j++) 
     { 
      col.Add(j); 
     } 

     col.CompleteAdding(); 

    }); 

    foreach (var item in col.GetConsumingEnumerable()) 
    { 
     //item is removed from the collection here, do something 
     Console.WriteLine(item); 
    } 
} 
9

Phương pháp # 1:

Phương pháp đơn giản nhất và hiệu quả nhất là tạo ra một phần quan trọng đối với các độc giả và nhà văn.

// Writer 
lock (aList) 
{ 
    aList.Remove(item); 
} 

// Reader 
lock (aList) 
{ 
    foreach (T name in aList) 
    { 
    name.doSomething(); 
    } 
} 

Phương pháp # 2:

này tương tự như phương pháp # 1, nhưng thay vì giữ khóa cho toàn bộ thời gian của foreach vòng lặp bạn sẽ sao chép các bộ sưu tập đầu tiên và sau đó lặp qua các sao chép.

// Writer 
lock (aList) 
{ 
    aList.Remove(item); 
} 

// Reader 
List<T> copy; 
lock (aList) 
{ 
    copy = new List<T>(aList); 
} 
foreach (T name in copy) 
{ 
    name.doSomething(); 
} 

Phương pháp # 3:

Tất cả phụ thuộc vào tình hình cụ thể của bạn, nhưng cách tôi thường đối phó với điều này là để giữ cho các tài liệu tham khảo tổng thể để các bộ sưu tập không thay đổi. Bằng cách đó bạn không bao giờ phải đồng bộ hóa quyền truy cập ở phía người đọc. Phía nhà văn của sự vật cần một lock. Phía người đọc không cần gì có nghĩa là người đọc ở mức cao đồng thời. Điều duy nhất bạn cần làm là đánh dấu tham chiếu aListvolatile.

// Variable declaration 
object lockref = new object(); 
volatile List<T> aList = new List<T>(); 

// Writer 
lock (lockref) 
{ 
    var copy = new List<T>(aList); 
    copy.Remove(item); 
    aList = copy; 
} 

// Reader 
List<T> local = aList; 
foreach (T name in local) 
{ 
    name.doSomething(); 
} 
+0

Tôi thích nhiều giải pháp ở đây, nhưng cần lưu ý trong hai ví dụ đầu tiên rằng 'aList' phải là một đối tượng bên trong của lớp nếu không bế tắc có thể xảy ra. –

+0

@DerekW: Vâng, đó là một thực hành tốt và được chú ý. Trong thực tế nó không có khả năng thực sự gây ra một bế tắc bởi vì các kịch bản điển hình cho những sự cố như vậy (ổ khóa lồng nhau, vv) không được chơi ở đây. Vì vậy, trừ khi một đoạn mã khác hoàn toàn lạm dụng 'aList', điều tồi tệ nhất có thể xảy ra là tăng ganh đua. Tôi nghĩ rằng toàn bộ cuộc tranh luận "khóa (này)" hơi bị thổi phồng và nghiêng một chút quá xa về phía hoang tưởng hơn một số sẽ thừa nhận. Một lần nữa, nó vẫn là thực hành tốt để tránh những thành ngữ như vậy. –

+0

Tôi cũng nên chỉ ra trong khi tôi đang suy nghĩ về nó rằng # 3 cần được theo dõi * chính xác * như được trình bày. Bất kỳ độ lệch nào (như quyết định rằng việc gán cho 'local' có thể được bỏ qua) có thể sẽ dẫn đến thất bại ngẫu nhiên và ngoạn mục. Nó thậm chí có thể rip toàn bộ trong không thời gian cho tất cả tôi biết. –

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