2012-03-01 22 views
7

Tôi đang cố gắng viết một phương pháp an toàn chỉ có thể được gọi một lần (mỗi trường hợp đối tượng). Một ngoại lệ nên được ném nếu nó đã được gọi trước đó.Làm thế nào để: Viết một phương pháp an toàn chỉ có thể được gọi một lần?

Tôi đã đưa ra hai giải pháp. Cả hai đều đúng không? Nếu không, có gì sai với họ?

  1. Với lock:

    public void Foo() 
    { 
        lock (fooLock) 
        { 
         if (fooCalled) throw new InvalidOperationException(); 
         fooCalled = true; 
        } 
        … 
    } 
    private object fooLock = new object(); 
    private bool fooCalled; 
    
  2. Với Interlocked.CompareExchange:

    public void Foo() 
    { 
        if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1) 
         throw new InvalidOperationException(); 
        … 
    } 
    private int fooCalled; 
    

    Nếu tôi không nhầm, giải pháp này có lợi thế là lock-free (dường như không có liên quan gì trong trường hợp của tôi) và điều đó đòi hỏi ít trường riêng hơn .

Tôi cũng mở cho các ý kiến ​​hợp lý về giải pháp nào nên được ưu tiên và đề xuất thêm nếu có cách tốt hơn.

Trả lời

5

Giải pháp Interlocked.CompareExchange của bạn trông đẹp nhất và (như bạn đã nói) không có khóa. Nó cũng ít phức tạp hơn các giải pháp khác. Khóa là khá nặng, trong khi CompareExchange có thể được biên dịch xuống một lệnh cpu CAS duy nhất. Tôi nói đi với cái đó.

+0

Trong sự tò mò: Khi bạn nói rằng nó "ít phức tạp", bạn dường như ám chỉ đến tất cả những gì đang diễn ra đằng sau người khiếm thị; bạn sẽ đánh giá khả năng đọc/dễ hiểu về cấu trúc 'Interlocked.ExchangeCompare' cho một lập trình viên trung bình như thế nào? – stakx

+1

@stakx: đó là ý kiến ​​dành cho. Khi một lập trình viên gặp một cái gì đó họ không hiểu, họ nên tìm kiếm nó để họ hiểu nó. Đó là cách họ trở thành một lập trình viên tốt hơn. – thecoop

+0

@thecoop Tôi không đồng ý, có đó là giải pháp chính xác nhất, nhưng nó không đơn giản, bạn cần biết về các hoạt động nguyên tử ... Đây là một quá trình khởi tạo và tôi khuyên bạn nên làm theo các mẫu khởi tạo được sử dụng rộng rãi (ví dụ: kiểm tra khóa). Ngoài ra các mẫu này ngăn bạn thiếu một cái gì đó mà có thể dễ dàng xảy ra khi làm luồng. – ntziolis

0

Các đôi khóa patter kiểm tra là những gì bạn đang sau:

Đây là những gì bạn đang sau:

class Foo 
{ 
    private object someLock = new object(); 
    private object someFlag = false; 


    void SomeMethod() 
    { 
    // to prevent locking on subsequent calls   
    if(someFlag) 
     throw new Exception(); 

    // to make sure only one thread can change the contents of someFlag    
    lock(someLock) 
    { 
     if(someFlag) 
     throw new Exception(); 

     someFlag = true;      
    } 

    //execute your code 
    } 
} 

Nói chung khi tiếp xúc với những vấn đề như thế này cố gắng và làm theo cũng biết patters như một trong những ở trên.
Điều này làm cho nó dễ nhận biết và ít bị lỗi hơn vì bạn ít có khả năng bỏ sót điều gì đó hơn khi theo dõi một mẫu, đặc biệt là khi nói đến luồng.
Trong trường hợp của bạn đầu tiên nếu không có nhiều ý nghĩa nhưng thường bạn sẽ muốn thực thi logic thực tế và sau đó đặt cờ. Một chuỗi thứ hai sẽ bị chặn trong khi bạn đang thực thi mã (có thể khá tốn kém) của bạn.

Về mẫu thứ hai:
Có đúng, nhưng đừng làm cho nó phức tạp hơn. Bạn nên có lý do rất tốt để không sử dụng khóa đơn giản và trong tình huống này nó làm cho mã phức tạp hơn (bởi vì Interlocked.CompareExchange() ít được biết đến) mà không đạt được bất kỳ thứ gì (như bạn đã chỉ ra là khóa ít hơn khóa để đặt cờ boolean không thực sự một lợi ích trong trường hợp này).

+0

** 1. ** Điều này dường như làm cho việc đọc/ghi 'someFlag' không nguyên tử. Bạn có chắc chắn điều này là chính xác? ** 2. ** Còn giải pháp dựa trên 'Interlocked.CompareExchange' thì sao? – stakx

+0

sry quên dòng quan trọng nhất – ntziolis

+0

Hmm ... đang khóa một vấn đề như thế nếu bạn định ném một ngoại lệ? – stakx

-1
Task task = new Task((Action)(() => { Console.WriteLine("Called!"); })); 
    public void Foo() 
    { 
     task.Start(); 
    } 

    public void Bar() 
    { 
     Foo(); 
     Foo();//this line will throws different exceptions depends on 
       //whether task in progress or task has already been completed 
    }  
+1

Xin lỗi để nói rằng điều này không trả lời được câu hỏi. (Btw., Tôi biết Thư viện song song nhiệm vụ, nhưng nó không thể được sử dụng ở mọi nơi, ví dụ như phương pháp được đề cập thực hiện phương thức giao diện.) – stakx

+1

được sửa theo trường hợp của bạn – pamidur

+0

@pamidur bạn đang cố giải quyết vấn đề khác – ntziolis

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