2009-09-04 28 views
25

Tôi có đoạn mã sau đây:Lợi nhuận có đạt được trong C# thread-safe không?

private Dictionary<object, object> items = new Dictionary<object, object>; 
public IEnumerable<object> Keys 
{ 
    get 
    { 
     foreach (object key in items.Keys) 
     { 
      yield return key; 
     } 
    } 
} 

Chủ đề này có an toàn không? Nếu không, tôi phải đặt lock quanh vòng lặp hoặc yield return?

Dưới đây là những gì tôi có nghĩa là:

thread1 truy cập vào Keys tài sản trong khi thread2 thêm một mục vào từ điển cơ bản. Thread1 có bị ảnh hưởng bởi việc thêm Thread2 không?

+0

dụ thứ hai của bạn sẽ khóa, trả lại một đếm được sau đó mở khóa. Và bạn sẽ lặp lại SAU KHI mở khóa. – Guillaume

+0

Ồ, bạn nói đúng. Tôi chỉ nhận thấy đó là một ví dụ xấu. Tôi sẽ chỉnh sửa câu hỏi. – Albic

+0

Chỉ để làm sáng tỏ một chút: Chủ đề an toàn thông qua khóa là tốn kém, do đó, nó không có ý nghĩa để tự động khóa trên mọi hành động trừ khi bạn yêu cầu một cách rõ ràng. –

Trả lời

7

OK, tôi đã thực hiện một số thử nghiệm và có kết quả thú vị.

Dường như đó là vấn đề của điều tra bộ sưu tập cơ bản hơn từ khóa yield. Điều tra viên (thực tế là phương pháp MoveNext) của nó ném (nếu được thực hiện đúng) một InvalidOperationException vì điều tra đã thay đổi. Theo số MSDN documentation of the MoveNext method, đây là hành vi mong đợi.

Vì việc liệt kê thông qua bộ sưu tập thường không an toàn chỉ là yield return cũng không phải là.

20

Chính xác thì bạn có ý nghĩa gì với chủ đề an toàn?

Bạn chắc chắn không nên thay đổi từ điển trong khi bạn đang lặp lại từ điển, dù trong cùng một chuỗi hay không.

Nếu điển đang được truy cập trong nhiều bài nói chung, gọi nên đưa ra một khóa (một trong những cùng bao gồm tất cả các truy cập) để họ có thể khóa trong suốt thời gian lặp lại qua kết quả.

EDIT: Để trả lời chỉnh sửa của bạn, không có nó trong không có cách nào tương ứng với mã khóa. Không có khóa tự động lấy ra bởi một khối lặp - và làm thế nào nó sẽ biết về syncRoot anyway?

Hơn nữa, chỉ cần khóa sự trở lại của IEnumerable<TKey> cũng không làm cho chuỗi an toàn - bởi vì khóa chỉ ảnh hưởng đến khoảng thời gian khi nó là trả về chuỗi đó chứ không phải khoảng thời gian nó được lặp lại.

+0

Có, tôi có nghĩa là từ điển thay vì Danh sách. Chỉnh sửa đầu tiên là sai (xin lỗi về điều đó) và tôi đã xóa nó. Tôi đã thêm hành vi mong đợi vào câu hỏi. – Albic

18

Check-out bài này vào những gì xảy ra đằng sau hậu trường với từ khóa yield:

Behind the scenes of the C# yield keyword

Nói tóm lại - trình biên dịch có từ khóa lợi nhuận của bạn và tạo ra toàn bộ một lớp trong IL để hỗ trợ các chức năng. Bạn có thể kiểm tra trang sau khi nhảy và kiểm tra các mã được tạo ra ... và mã đó có vẻ như nó theo dõi id thread để giữ cho mọi thứ an toàn.

+0

+1 cho liên kết thực sự hữu ích. – Albic

3

Tôi tin là vậy, nhưng tôi không thể tìm thấy tham chiếu xác nhận nó. Mỗi lần bất kỳ thread gọi foreach trên một iterator, một mới thread địa phương * dụ của IEnumerator cơ bản sẽ được tạo ra, do đó, không nên có bất kỳ "chia sẻ" bộ nhớ ... Điện thoại

  • Chủ đề địa phương - Theo nghĩa là biến tham chiếu của nó được đưa vào khung ngăn xếp phương thức trên chủ đề đó
+0

Vâng, nhưng nếu gọi GetEnumerator và sau đó chia sẻ IEnumerator? Ông nên làm rõ những gì ông đang làm với chủ đề của mình. – Guillaume

3

Tôi tin rằng việc triển khai lợi nhuận là an toàn chỉ. Thật vậy, bạn có thể chạy chương trình đơn giản đó ở nhà và bạn sẽ thấy rằng trạng thái của phương thức listInt() được lưu và phục hồi đúng cho mỗi luồng mà không có hiệu ứng cạnh từ các luồng khác.

public class Test 
{ 
    public void Display(int index) 
    { 
     foreach (int i in listInt()) 
     { 
      Console.WriteLine("Thread {0} says: {1}", index, i); 
      Thread.Sleep(1); 
     } 

    } 

    public IEnumerable<int> listInt() 
    { 
     for (int i = 0; i < 5; i++) 
     { 
      yield return i; 
     } 
    } 
} 

class MainApp 
{ 
    static void Main() 
    { 
     Test test = new Test(); 
     for (int i = 0; i < 4; i++) 
     { 
      int x = i; 
      Thread t = new Thread(p => { test.Display(x); }); 
      t.Start(); 
     } 

     // Wait for user 
     Console.ReadKey(); 
    } 
} 
+0

+1. Tôi cũng chỉ xác minh rằng các máy trạng thái của trình vòng lặp được tạo ra bởi trình biên dịch C# 4.0 là luồng an toàn. –

2
class Program 
{ 
    static SomeCollection _sc = new SomeCollection(); 

    static void Main(string[] args) 
    { 
     // Create one thread that adds entries and 
     // one thread that reads them 
     Thread t1 = new Thread(AddEntries); 
     Thread t2 = new Thread(EnumEntries); 

     t2.Start(_sc); 
     t1.Start(_sc); 
    } 

    static void AddEntries(object state) 
    { 
     SomeCollection sc = (SomeCollection)state; 

     for (int x = 0; x < 20; x++) 
     { 
      Trace.WriteLine("adding"); 
      sc.Add(x); 
      Trace.WriteLine("added"); 
      Thread.Sleep(x * 3); 
     } 
    } 

    static void EnumEntries(object state) 
    { 
     SomeCollection sc = (SomeCollection)state; 
     for (int x = 0; x < 10; x++) 
     { 
      Trace.WriteLine("Loop" + x); 
      foreach (int item in sc.AllValues) 
      { 
       Trace.Write(item + " "); 
      } 
      Thread.Sleep(30); 
      Trace.WriteLine(""); 
     } 
    } 
} 

class SomeCollection 
{ 
    private List<int> _collection = new List<int>(); 
    private object _sync = new object(); 

    public void Add(int i) 
    { 
     lock(_sync) 
     { 
      _collection.Add(i); 
     } 
    } 


    public IEnumerable<int> AllValues 
    { 
     get 
     { 
      lock (_sync) 
      { 
       foreach (int i in _collection) 
       { 
        yield return i; 
       } 
      } 
     } 
    } 
} 
+0

Đây là một ví dụ đơn giản cho thấy rằng năng suất KHÔNG phải là chủ đề an toàn bởi chính nó. Theo văn bản, nó là chủ đề an toàn nhưng nếu bạn nhận xét khóa (_sync) trong AllValues, bạn sẽ có thể xác minh rằng nó không phải là chủ đề an toàn bằng cách chạy nó một vài lần. Nếu bạn nhận được một InvalidOperationException nó chứng minh rằng nó không phải là chủ đề an toàn. – sjp

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