0
我有一個枚舉需要很長時間才能獲得下一個值。如何從IEnumerable封裝維護項目列表,延遲很長
我想包裝那個枚舉,以便我得到一個緩存結果的枚舉。
我還想讓它在另一個線程上做額外的加載(報告它到達集合的末尾)。即如果它有10個緩存值,並且我枚舉它報告10,然後啓動一個線程來獲得下一個線程,所以當再次枚舉時有11個緩存值。
我有這麼遠低於爲(與測試中,它在底部代碼):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class CachedEnumerable<T> : IEnumerable<T>
{
public class CachedEnumerator : IEnumerator<T>
{
private IEnumerator<T> _UnderlyingEnumerator;
public event EventHandler Disposed;
public CachedEnumerator(IEnumerator<T> UnderlyingEnumerator)
{
_UnderlyingEnumerator = UnderlyingEnumerator;
}
public T Current
{
get { return _UnderlyingEnumerator.Current; }
}
public void Dispose()
{
_UnderlyingEnumerator.Dispose();
if (Disposed != null)
Disposed(this, new EventArgs());
}
object System.Collections.IEnumerator.Current
{
get { return _UnderlyingEnumerator.Current; }
}
public bool MoveNext()
{
return _UnderlyingEnumerator.MoveNext();
}
public void Reset()
{
_UnderlyingEnumerator.Reset();
}
}
// The slow enumerator.
private IEnumerator<T> _SourceEnumerator;
// Whether we're currently already getting the next item.
private bool _GettingNextItem = false;
// Whether we've got to the end of the source enumerator.
private bool _EndOfSourceEnumerator = false;
// The list of values we've got so far.
private List<T> _CachedValues = new List<T>();
// An object to lock against, to protect the cached value list.
private object _CachedValuesLock = new object();
// A reset event to indicate whether the cached list is safe, or whether we're currently enumerating over it.
private ManualResetEvent _CachedValuesSafe = new ManualResetEvent(true);
public CachedEnumerable(IEnumerable<T> Source)
{
_SourceEnumerator = Source.GetEnumerator();
}
private void Enum_Disposed(object sender, EventArgs e)
{
// The cached list is now safe (because we've finished enumerating).
_CachedValuesSafe.Set();
if (!_EndOfSourceEnumerator && !_GettingNextItem)
{
_GettingNextItem = true;
ThreadPool.QueueUserWorkItem((SourceEnumeratorArg) =>
{
var SourceEnumerator = SourceEnumeratorArg as IEnumerator<T>;
if (SourceEnumerator.MoveNext())
{
_CachedValuesSafe.WaitOne(); // Wait for any enumerator to finish
lock (_CachedValuesLock)
{
_CachedValues.Add(SourceEnumerator.Current);
}
}
else
{
_EndOfSourceEnumerator = true;
}
_GettingNextItem = false;
}, _SourceEnumerator);
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
lock (_CachedValuesLock)
{
var Enum = new CachedEnumerator(_CachedValues.GetEnumerator());
Enum.Disposed += new EventHandler(Enum_Disposed);
_CachedValuesSafe.Reset();
return Enum;
}
}
}
class Program
{
public static IEnumerable<int> SlowNumbers()
{
int i = 0;
while (true)
{
Thread.Sleep(1000);
yield return i++;
}
}
static void Main(string[] args)
{
var SlowNumbersEnumerator = new CachedEnumerable<int>(SlowNumbers());
while (true)
{
foreach (var i in SlowNumbersEnumerator)
{
Console.WriteLine(i);
Thread.Sleep(100);
}
}
}
}
}
我的問題是,我得到了The collection has been modified
錯誤,因爲工作線程添加到列表中,而它的被列舉。然而,通過我使用ManualResetEvent
我以爲我在防範這一點。
我在做什麼錯了?
唉唉我明白了,所以我的理解是我的解決方法是檢查等待句柄之前獲取鎖,從而防止另一個線程開始枚舉之前,我添加我的項目。 –
也要注意,你當前的代碼沒有考慮到同時進行多次枚舉的可能性(兩次連續調用GetEnumerator,然後枚舉這兩次),但只要你不用linq玩兩次它不會經常出現在正常的代碼中) –
謝謝,我知道,只是試圖讓它最初排序爲簡單情況。現在已經轉移到這個問題上了! –