开发者

How to encapsulate maintaining list of items from IEnumerable with a long delay

开发者 https://www.devze.com 2023-03-21 11:20 出处:网络
I have an enumerable that takes a long time to get the next value. I\'m trying to wrap that enumerable so that I get an enumerable that caches the results.

I have an enumerable that takes a long time to get the next value.

I'm trying to wrap that enumerable so that I get an enumerable that caches the results.

I'd also like it to do additional loading on another thread (reporting it reached the end of the collection). i.e. if it has 10 cached values, and i enumerate it reports 10, then starts a thread to get the next one so when enumerated again there's 11 cached values.

What i have so far is below (with code that tests it at the bottom):

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.Re开发者_运维技巧set();

                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);
                }
            }
        }
    }
}

My problem is that i get the The collection has been modified error, because the worker thread is adding to the list while it's being enumerated. However, through my use of a ManualResetEvent i thought i was guarding against this.

What am i doing wrong?


(making my comment an answer, no way to format comments correctly)

  1. Thread A is enumerating | Thread B is on _CachedValuesSafe.WaitOne();
  2. Thread A finish enumerating (releasing the WaitHandle)
  3. Thread A started a new enumeration getting the lock | Thread B want to aquire lock
  4. Thread A initialize the enumeration| Thread B wait for the lock
  5. Thread A return from the GetEnumerator() call and release the lock | Thread B acquire the lock
  6. Thread A is enumerating the collection | Thread B execute Add, modifying the collection
  7. Thread A enumerate the next value and throw an exception
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号