收集合同和线程

时间:2010-10-06 16:10:02

标签: c# static-analysis code-contracts

假设我有一个自定义集合类,它提供了一些内部线程同步。例如,简化的Add方法可能如下所示:

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

最新的代码合同抱怨CodeContracts: ensures unproven: this.Count >= Contract.OldValue(this.Count)。问题是这真的无法证明。我可以确保在内部锁定内,Count将大于其先前的值。但是,在方法的退出处,我无法确保这一点。退出锁之后,在方法完成之前,另一个线程可以发出两个Removes(可能是不同的元素),使合同无效。

这里的基本问题是收集合同只能在特定的锁定上下文中被认为是有效的,并且只有在整个应用程序中一致地使用锁定才能对集合进行所有访问。我的集合必须在多个线程中使用(非冲突的Add和Remove是一个有效的用例),但我仍然希望实现ICollection<T>。即使我知道我不能,我是否应该假装我能够满足这个确保要求的假设?令我感到震惊的是,BCL系列中没有一个能够真正确保这一点。


编辑:

基于一些进一步的调查,听起来最大的问题是合同重写者可能引入不正确的断言,导致运行时失败。基于此,我认为我唯一的选择是将我的接口实现限制为IEnumerable<T>,因为ICollection<T>上的契约意味着实现类不能提供内部线程同步(访问必须始终在外部同步。)对于我的特定情况,这是可以接受的(所有希望改变集合的客户端都直接了解类类型),但我非常有兴趣听听是否有其他解决方案。

2 个答案:

答案 0 :(得分:2)

正如您所暗示的,没有任何实施者可以满足此合同。实际上通常面对多线程,除非合同可以像:

 Take Lock
 gather Old Stuff

 work

 check Contract, which may compare Old Stuff
 Release Lock

我不知道如何兑现任何合同。从我所看到的here来看,这个区域还没有完全成熟。

我认为使用Assume是你能做的最好的,实际上你说的是“通过调用Add我正在做合同所期望的”。

答案 1 :(得分: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这样的小整数类型进行测试。确保使用不会溢出的整数类型。