2011-07-20 49 views
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我以爲我在防範這一點。

我在做什麼錯了?

回答

1

(使我的評論的答案,沒辦法正確地格式化評論)

  1. 線程A是枚舉|線程B在_CachedValuesSafe.WaitOne();
  2. 線程完成枚舉(釋放WaitHandle)
  3. 線程A啓動了一個新的枚舉獲取鎖|線程B想要獲得鎖定
  4. 線程初始化枚舉|線程B等待鎖定
  5. 線程從GetEnumerator()調用返回並釋放鎖定|線程B獲取鎖定
  6. 線程A正在枚舉集合|線程B執行Add,修改集合
  7. 線程A列舉下一個值,並拋出一個異常
+0

唉唉我明白了,所以我的理解是我的解決方法是檢查等待句柄之前獲取鎖,從而防止另一個線程開始枚舉之前,我添加我的項目。 –

+0

也要注意,你當前的代碼沒有考慮到同時進行多次枚舉的可能性(兩次連續調用GetEnumerator,然後枚舉這兩次),但只要你不用linq玩兩次它不會經常出現在正常的代碼中) –

+0

謝謝,我知道,只是試圖讓它最初排序爲簡單情況。現在已經轉移到這個問題上了! –