C# 同步IEnumerator<;T>;

C# 同步IEnumerator<;T>;,c#,synchronization,multithreading,enumerator,C#,Synchronization,Multithreading,Enumerator,我正在创建一个定制的SynchronizedCollection类,以便为我的WPF应用程序创建一个同步的可观察集合。同步是通过ReaderWriterLockSlim提供的,在大多数情况下,ReaderWriterLockSlim很容易应用。我遇到的问题是如何提供集合的线程安全枚举。我创建了一个自定义的IEnumerator嵌套类,如下所示: private class SynchronizedEnumerator : IEnumerator<T> {

我正在创建一个定制的
SynchronizedCollection
类,以便为我的WPF应用程序创建一个同步的可观察集合。同步是通过ReaderWriterLockSlim提供的,在大多数情况下,ReaderWriterLockSlim很容易应用。我遇到的问题是如何提供集合的线程安全枚举。我创建了一个自定义的
IEnumerator
嵌套类,如下所示:

    private class SynchronizedEnumerator : IEnumerator<T>
    {
        private SynchronizedCollection<T> _collection;
        private int _currentIndex;

        internal SynchronizedEnumerator(SynchronizedCollection<T> collection)
        {
            _collection = collection;
            _collection._lock.EnterReadLock();
            _currentIndex = -1;
        }

        #region IEnumerator<T> Members

        public T Current { get; private set;}

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            var collection = _collection;
            if (collection != null)
                collection._lock.ExitReadLock();

            _collection = null;
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            var collection = _collection;
            if (collection == null)
                throw new ObjectDisposedException("SynchronizedEnumerator");

            _currentIndex++;
            if (_currentIndex >= collection.Count)
            {
                Current = default(T);
                return false;
            }

            Current = collection[_currentIndex];
            return true;
        }

        public void Reset()
        {
            if (_collection == null)
                throw new ObjectDisposedException("SynchronizedEnumerator");

            _currentIndex = -1;
            Current = default(T);
        }

        #endregion
    }
public void EnumerateInLock(Action<IEnumerable<T>> action)
{
    var e = new EnumeratorImpl(this);

    try
    {
        _lock.EnterReadLock();
        action(e);
    }
    finally
    {
        e.Dispose();
        _lock.ExitReadLock();
    }
}
私有类SynchronizedEmerator:IEnumerator
{
私有同步集合_集合;
私有int_currentIndex;
内部SynchronizedEmerator(SynchronizedCollection集合)
{
_收集=收集;
_集合。_lock.EnterReadLock();
_currentIndex=-1;
}
#区域IEnumerator成员
公共T当前{get;私有集;}
#端区
#区域IDisposable成员
公共空间处置()
{
var集合=_集合;
if(集合!=null)
集合。_lock.exitradlock();
_集合=空;
}
#端区
#区域IEnumerator成员
对象System.Collections.IEnumerator.Current
{
获取{返回当前;}
}
公共图书馆
{
var集合=_集合;
if(集合==null)
抛出新的ObjectDisposedException(“SynchronizedEmerator”);
_currentIndex++;
if(_currentIndex>=collection.Count)
{
电流=默认值(T);
返回false;
}
当前=集合[_currentIndex];
返回true;
}
公共无效重置()
{
if(_collection==null)
抛出新的ObjectDisposedException(“SynchronizedEmerator”);
_currentIndex=-1;
电流=默认值(T);
}
#端区
}
然而,我担心的是,如果不释放枚举器,锁将永远不会被释放。在大多数用例中,这不是问题,因为foreach应该正确地调用Dispose。但是,如果使用者检索显式枚举器实例,则可能会出现问题。如果显式使用枚举器,我唯一的选项是使用警告实现器来记录类,提醒使用者调用Dispose,还是有办法在完成过程中安全地释放锁?我不这么认为,因为终结器甚至不在同一个线程上运行,但我很好奇是否有其他方法可以改进它


编辑


在仔细考虑了一下并阅读了回复后(特别感谢Hans),我认为这绝对是个坏主意。最大的问题其实不是忘记处理,而是一个悠闲的消费者在枚举时造成死锁。现在,我只读取足够长的锁以获取副本并返回副本的枚举数。

在必须使用的
IDisposable
实现中,您创建了一个受保护的
Dispose(bool-managed)
方法,该方法始终处理您使用的非托管资源。通过从终结器调用受保护的
Dispose(false)
方法,可以根据需要释放锁。锁已被管理,是否仅在调用
dispose(true)
时才处理它,其中
true
表示需要处理托管对象。否则,当显式调用public
Dispose()
时,它会调用受保护的
Dispose(true)
以及
GC.SuppressFinalize(this)
,以阻止终结器运行(因为不再有任何可处理的内容)


因为您永远不知道用户何时使用枚举器,所以除了记录用户必须处理对象之外,您别无选择。您可能希望建议用户使用
using(){…}
构造,该构造在完成时自动处理对象。

我最近不得不这样做。我这样做的方式是对它进行抽象,这样就有了一个内部对象(引用),它包含了实际的列表/数组和计数(以及一个
GetEnumerator()
实现);然后我可以通过以下方式进行无锁、线程安全的枚举:

public IEnumerator<T> GetEnumerator() { return inner.GetEnumerator();}
public IEnumerator GetEnumerator(){return inner.GetEnumerator();}
Add
etc需要同步,但是它们更改
内部
引用(因为引用更新是原子的,所以不需要同步
GetEnumerator()
)。这意味着任何枚举器都将返回与创建枚举器时相同数量的项


当然,我的场景很简单,而且我的列表是只添加,如果你需要支持mutate/remove,那么它就要复杂得多。

这对我来说太容易出错了。它鼓励以代码读者不清楚的方式隐式/静默地取出锁的情况,并使它像y:关于接口的一个关键事实将被误解

通常,复制公共模式是一个好主意—用
IEnumerable
表示一个可枚举的集合,当您使用它时,该集合将被释放—但不幸的是,取出锁的附加成分会产生很大的不同

我建议理想的方法是根本不提供线程间共享集合的枚举。尝试设计整个系统,使其不需要。显然,这有时会是一个疯狂的白日梦

因此,下一个最好的方法是定义一个上下文,当锁存在时,
IEnumerable
暂时可用:

public class SomeCollection<T>
{
    // ...

    public void EnumerateInLock(Action<IEnumerable<T>> action) ...

    // ...
}
这使得锁的生命周期由一个作用域(由lambda主体表示,工作方式非常类似于
lock
语句)和im显式声明
public void EnumerateInLock(Action<IEnumerable<T>> action)
{
    var e = new EnumeratorImpl(this);

    try
    {
        _lock.EnterReadLock();
        action(e);
    }
    finally
    {
        e.Dispose();
        _lock.ExitReadLock();
    }
}
IEnumerable<C> keepForLater = null;
someCollection.EnumerateInLock(e => keepForLater = e);

foreach (var item in keepForLater)
{
    // aha!
}