集合合约和线程

发布于 2024-09-26 12:36:47 字数 959 浏览 5 评论 0原文

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

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

最新的 Code Contracts 抱怨 CodeContracts: 确保未经验证:this.Count >= Contract.OldValue(this.Count)。问题是这确实无法证明。我可以确保在锁内部,Count 将大于其先前的值。但是,在方法的出口处我无法确保这一点。退出锁定后且在方法完成之前,另一个线程可能会发出两个删除(可能是不同的元素),从而使契约无效。

这里的根本问题是,只有在特定的锁定上下文中,并且只有在整个应用程序中对集合的所有访问一致地使用锁定时,集合契约才能被视为有效。我的集合必须从多个线程使用(不冲突的添加和删除是一个有效的用例),但我仍然想实现 ICollection。我是否应该简单地假装我可以通过假设满足此确保要求,即使我知道我不能?令我惊讶的是,BCL 收藏中也没有一个能够真正确保这一点。


编辑:

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

Suppose I have a custom collection class that provides some internal thread synchronization. For instance, a simplified Add method might look like this:

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

The latest Code Contracts complains that CodeContracts: ensures unproven: this.Count >= Contract.OldValue(this.Count). The problem is that this really can't be proven. I can ensure that, internally, within the lock, Count will be greater than its previous value. I cannot ensure this, however, at the exit of the method. After the lock is exited and before the method completes, another thread could issue two Removes (likely of different elements), invalidating the contract.

The fundamental issue here is that the collection contracts can only be considered valid from within a particular locking context and only if the locking is used consistently throughout the application for all access to the collection. My collection must be used from multiple threads (with non-conflicting Add and Remove being a valid use case), but I would still like to implement ICollection<T>. Should I simply pretend that I can meet this ensures requirement with an Assume, even though I know I can't? It strikes me that none of the BCL collections can actually ensure this either.


EDIT:

Based on some further investigation, it sounds like the biggest issue is that the contract rewriter can introduce incorrect assertions, leading to runtime failures. Based on this, I think my only option is to restrict my interface implementation to IEnumerable<T>, as the contract on ICollection<T> implies that the implementing class cannot provide internal thread synchronization (access must always be synchronized externally.) This is acceptable for my particular case (all clients that wish to mutate the collection know about the class type directly), but I'm definitely interested to hear if there are other solutions to this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

心房的律动 2024-10-03 12:36:47

正如您所暗示的,没有实施者可以履行该合同。事实上,通常面对多线程,除非可以像这样应用合同:

 Take Lock
 gather Old Stuff

 work

 check Contract, which may compare Old Stuff
 Release Lock

我不知道如何遵守任何合同。从我可以看到这里 这是一个尚未完全烘烤的区域。

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

As you imply, no implementer can meet this contract. Indeed generally in the face of multi-threading unless the contract can be applied like:

 Take Lock
 gather Old Stuff

 work

 check Contract, which may compare Old Stuff
 Release Lock

I don't see how any contract could be honoured. From what I can see here this is an area that's not fully baked yet.

I think that using an Assume is the best you can do, in effect you are saying "by calling Add I am doing what the contract expects".

剧终人散尽 2024-10-03 12:36:47
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 等小整数类型进行测试。确保使用不会引发溢出的整数类型。

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);
        }
    }
}

Warning: do not use grater than less than comparisons; The counts will overflow, but these contracts should work in that case. Test with a small integer type like int8. Make sure to use an integer type that does not throw on overflow.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文