我知道,怎麼諷刺的是,我遇到了死鎖比較麻煩,而實現這個ThreadsafeCollection
類比我將不得不否則......在任何情況下:僵局定製ThreadsafeCollection類
我試圖創建一個簡單的集合類,爲添加,刪除和迭代維護自己的線程同步。除了通過foreach
或Linq迭代收集之外,一切似乎都很好。它使用專用ReaderWriterLockSlim
,其在Add()
和Remove()
方法中調用EnterWriteLock()/ExitWriteLock()
,並在GetEnumerator()
的呼叫中返回SafeEnumerator<T>
。 SafeEnumerator<T>
執行IEnumerator<T>
並通過它的構造函數接收對前述ReaderWriterLockSlim
的引用,以便它可以在構建過程中調用EnterReadLock()
,並在Dispose()
中調用ExitReadLock()
。
我注意到(通過體驗死鎖和驗證ExitReadLock()
(因此SafeEnumerator.Dispose()
)不會被調用)的問題是,集合有時無法獲得writelocks,因爲一個或多個線程不會釋放他們readlocks(通過安全枚舉器)。儘管我的ReaderWRiterLockSlim
是用LockRecursionPolicy.SupportsRecursion
屬性構建的,但這是事實。
在我的代碼,遍歷ThreadsafeCollection
唯一的地方用任何一foreach
或LINQ表達這樣做,這是我的理解是獲得(安全)枚舉這種方式不要求Dispose()
的顯式調用,甚至在迭代期間出現異常的情況下。
我的問題是:我誤解了foreach/LINQ/dispose一起工作的方式嗎?由於線程僅通過foreach或LINQ獲得枚舉數,因此線程無法調用SafeEnumerator.Dispose()
的原因是什麼?
僅供參考,我已在下面發佈了我的ThreadsafeCollection
和SafeEnumerator
的相關部分。
感謝您的幫助!
ThreadsafeCollection:
public class ThreadsafeCollection<T> : IEnumerable<T>
{
private ICollection<T> _collection;
private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public ThreadsafeCollection()
{
_collection = new Collection<T>();
}
public void Add(T item)
{
_lock.EnterWriteLock();
try
{
_collection.Add(item);
}
finally
{
_lock.ExitWriteLock();
}
}
public bool Remove(T item)
{
_lock.EnterWriteLock();
try
{
return _collection.Remove(item);
}
finally
{
_lock.ExitWriteLock();
}
}
public IEnumerator<T> GetEnumerator()
{
return new SafeEnumerator<T>(_collection.GetEnumerator(), _lock);
}
}
SafeEnumerator:
public class SafeEnumerator<T> : IEnumerator<T>
{
private readonly IEnumerator<T> _inner;
private readonly ReaderWriterLockSlim _lock;
public SafeEnumerator(IEnumerator<T> inner, ReaderWriterLockSlim collectionlock)
{
_inner = inner;
_lock = collectionlock;
_lock.EnterReadLock();
}
public void Dispose()
{
_lock.ExitReadLock();
}
public bool MoveNext()
{
return _inner.MoveNext();
}
public void Reset()
{
_inner.Reset();
}
public T Current
{
get { return _inner.Current; }
}
object IEnumerator.Current
{
get { return Current; }
}
}
您已經驗證您的枚舉總是列舉結束了嗎?如果它們停在中間(或者在枚舉的同一個線程的中間發生寫操作),那麼這將是一個簡單的死鎖方法。避免這種情況的一種方法是返回枚舉器,該枚舉器實際上在請求枚舉時包裝集合的快照。也許不適合你的目的,但顯然不太容易陷入僵局。 – dlev
我已驗證我的所有枚舉都已完成,這就是爲什麼這很令人困惑。我已決定使用替代解決方案,所以我放棄了試圖找出原因! – Tsherr