2010-10-06 81 views
9

假設我有一個自定義集合類,它提供了一些內部線程同步。例如,簡單的添加方法可能是這樣的:收集合同和線程

public void Add(T item) 
    { 
     _lock.EnterWriteLock(); 
     try 
     { 
      _items.Add(item); 
     } 
     finally 
     { 
      _lock.ExitWriteLock(); 
     } 
    } 

最新的代碼契約抱怨CodeContracts: ensures unproven: this.Count >= Contract.OldValue(this.Count)。問題是,這實在無法證明。我可以確保,在內部的鎖定內,Count會比先前的值大。但是,在方法退出時我無法確保這一點。鎖退出後,方法完成之前,另一個線程可以發出兩個刪除(可能是不同的元素),使合同無效。

這裏的根本問題是,集合合約只能在特定的鎖定上下文內被認爲是有效的,並且只有在整個應用程序中對所有集合的訪問一致地使用鎖定。我的集合必須從多個線程中使用(非衝突的Add和Remove是一個有效的用例),但我仍然想要實現ICollection<T>。我是否應該簡單地假裝我能滿足這個要求,即使我知道我不能做到這一點,但可以確保要求。這讓我覺得BCL的所有收藏都無法真正確保這一點。


編輯:

基於一些進一步的調查,這聽起來像最大的問題是,該合同重寫可以引進不正確的斷言,導致運行時故障。基於此,我認爲我唯一的選擇是限制我的接口實現爲IEnumerable<T>,因爲ICollection<T>上的合同意味着實現類不能提供內部線程同步(訪問必須始終在外部進行同步。)這對於我的特殊情況是可以接受的(所有希望改變集合的客戶都會直接瞭解類的類型),但是我非常有興趣知道是否有其他解決方案。

+0

如果您使用假設您仍然可能會收到運行時合同錯誤。 – 2012-02-10 15:09:21

回答

2

正如你所暗示的,沒有實施者能夠滿足這個合同。事實上一般在面對多線程的情況下,除非合同可以應用如:

Take Lock 
gather Old Stuff 

work 

check Contract, which may compare Old Stuff 
Release Lock 

我看不出有什麼合約可以兌現。從我所看到的here這是一個尚未完全烘焙的領域。

我認爲使用假設是最好的,你可以做的,實際上你是說「通過調用Add我正在做什麼合同期望」。

+0

另一個想法是:如果合約重寫器爲Ensures條件引入斷言,這意味着斷言在多線程使用下可能失敗...... – 2010-10-06 16:37:32

+1

這似乎在我給出的參考文獻中得到了承認。對我來說,這份合同看起來像是一個不錯的主意,尚未準備好迎接黃金時段。 – djna 2010-10-06 17:01:12

+0

想着這個更多,我想這可能是合理的。契約斷言基本上表明實現接口的類不是線程安全的,並且奇怪的是,你實際上是通過嘗試在集合級別提供內部線程安全來違反契約。我注意到.NET 4中的新併發集合不實現'ICollection '。 – 2010-10-06 17:41:09

0
using System.Diagnostics.Contracts; 

namespace ConsoleApplication1 
{ 
    class Class1 
    { 
     public int numberOfAdds { get; private set; } 
     public int numberOfRemoves { get; private set; } 
     public int Count 
     { 
      get 
      { 
       return numberOfAdds - numberOfRemoves; 
      } 
     } 

     public void Add() 
     { 
      Contract.Ensures(numberOfAdds == Contract.OldValue(numberOfAdds) + 1); 
     } 

     public void Remove() 
     { 
      Contract.Requires(Count >= 1); 
      Contract.Ensures(numberOfRemoves == Contract.OldValue(numberOfRemoves) + 1); 
     } 

     [ContractInvariantMethod] 
     void inv() 
     { 
      Contract.Invariant(Contract.Result<int>() == numberOfAdds - numberOfRemoves); 
     } 
    } 
} 

警告:不要使用比小於的比較;計數會溢出,但這些合同應該在這種情況下起作用。用像int8這樣的小整數類型進行測試。確保使用不會溢出的整數類型。