我已经使用 BlockingEnumerator 类完成了 ObservableCollection 多线程“ObsCollMt”,当使用“ForEach”枚举项目时,该类应该锁定底层 _list。
我修复了我的代码,但我不明白为什么它以新方式工作以及为什么它不以旧方式工作。有人能给我一个清晰的解释吗?
我包含了错误中隐含的两部分代码。在评论中:“NOT WORKING CODE”(旧方式)代码不起作用,因为它从不锁定“SyncRoot”,为什么?
ObsCollMt 获取阻塞迭代器的代码:
List<T> _list = new List<T>();
public object SyncRoot { get; } = new object();
// ******************************************************************
/// <summary>
/// This iterator block the collection for all the time you iterate in it.
/// For long job, it is preferable to use a copy of items.
/// </summary>
/// <returns></returns>
public virtual IEnumerator<T> GetEnumerator()
{
// WORKING CODE (lock the _list) (New way)
System.Threading.Monitor.Enter(SyncRoot);
return new BlockingIteratorWithActionBlockUnblock<T>(_list.GetEnumerator(), () => { }, () => System.Threading.Monitor.Exit(SyncRoot));
// NOT WORKING CODE (the _list is never blocked) (Old way)
//return new BlockingIteratorWithActionBlockUnblock<T>(_list.GetEnumerator(), () => System.Threading.Monitor.Enter(SyncRoot), () => System.Threading.Monitor.Exit(SyncRoot));
}
阻塞迭代器代码:
using System;
using System.Collections;
using System.Collections.Generic;
namespace General.Collection
{
public class BlockingIteratorWithActionBlockUnblock<T> : IDisposable, IEnumerator<T>
{
private IEnumerator<T> _iEnumerator = null;
private Action _actionUnlock = null;
public BlockingIteratorWithActionBlockUnblock(IEnumerator<T> iEnumerator, Action actionLock, Action actionUnlock)
{
if (iEnumerator == null) { throw new ArgumentException(nameof(iEnumerator)); }
if (actionLock == null) { throw new ArgumentException(nameof(actionLock)); }
if (actionUnlock == null) { throw new ArgumentException(nameof(actionUnlock)); }
_iEnumerator = iEnumerator;
_actionUnlock = actionUnlock;
actionLock();
}
public T Current
{
get { return _iEnumerator.Current; }
}
object IEnumerator.Current
{
get { return _iEnumerator.Current; }
}
public bool MoveNext()
{
return _iEnumerator.MoveNext();
}
public void Reset()
{
_iEnumerator.Reset();
}
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
// Use SupressFinalize in case a subclass
// of this type implements a finalizer.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// If you need thread safety, use a lock around these
// operations, as well as in your methods that use the resource.
if (!_disposed)
{
if (disposing)
{
_actionUnlock.Invoke();
var dispoable = _iEnumerator as IDisposable;
if (dispoable != null)
{
dispoable.Dispose();
_iEnumerator = null;
}
}
// Indicate that the instance has been disposed.
_disposed = true;
}
}
}
}
代码不起作用,您尝试在提供给 BlockingIteratorWithActionBlockUnblock 的 lambda 中使用 System.Threading.Monitor.Enter(SyncRoot) 锁定 SyncRoot。这里的问题是 lambda 本身是在迭代器设置期间执行的,而不是在实际迭代期间执行的。因此,锁定和解锁操作发生在迭代上下文之外,导致锁定无效。
在工作版本中,您在创建迭代器之前锁定 SyncRoot,并在迭代器完成后解锁它。这确保了锁定和解锁操作与迭代过程正确关联。
总而言之,关键的区别在于锁定和解锁操作发生的时间。在工作版本中,锁在迭代开始之前应用,并在迭代完成后释放,确保整个迭代都受到锁的保护。在不工作的版本中,锁定和解锁操作被安排为迭代器设置的一部分,而不是在实际迭代期间,从而使锁定无效。