IEnumerable
problem, as well as where the task came from.IEnumerable<T>
interface was perfectly suited for representing the “flow of objects”. It only remains to make sure that each thread has its own IEnumerable<T>
, and that it is the same IEnumerable<T>
(just like in the tasks about coins and glasses ). It is obvious that the nickname "clones" immediately stuck to such objects.enumerable.Next()
streams, a single object instance was readable. Such a solution has a peculiarity (this is rather a minus, but not terrible): all threads will wait for the slowest brother, that is, the reading will be synchronous. To be honest, I really hoped that someone had already written it for me. For example, Microsoft did something similar in its Parallel.Linq. However, at that time it was not possible to google anything like that - everything related to parallel processing of collections was related to parallel processing of parts of one collection (for example, Parallel.For()
), but not at all to the problem being solved. Well, I adore bicycles, a file in hands!yield return
and yield break
. Their application formed the basis of the decision.GetClone()
method, which returns a call to the method using the yield
. So that the reading was synchronized and no one lost anything, the factory remembers its readers and makes it so that the next object is read from the source IEnumerable
only after all readers receive their link to the copy of the previous object. This is achieved by the fact that each reader is assigned two WaitHandles - “I am ready to read” and “I have read”: private IEnumerable<T> _input; private IEnumerator<T> _inputEnumerator; private Dictionary<string, AutoResetEvent> _imReadyToRead; private Dictionary<string, AutoResetEvent> _iVeReadMyValue; private WaitHandle[] _ivAlreadyReadArray; private T _nextObject; private bool _hasCurrent; private bool _hasNext; ... private void GetNext() { if (!_hasCurrent) return; foreach (var ready in _imReadyToRead) ready.Value.Set(); do { WaitHandle.WaitAll(_ivAlreadyReadArray); // , _hasNext = _inputEnumerator.MoveNext(); // _nextObject = _inputEnumerator.Current; lock (_imReadyToRead) { if (!_hasNext) _hasCurrent = false; foreach (var ready in _imReadyToRead) ready.Value.Set(); // . } } while (_hasNext); }
IEnumerable
, which ( GetCloneInstance
?) Is returned by the GetCloneInstance
method. private T GetCurrent(string subscriber) { T toReturn; _imReadyToRead[subscriber].WaitOne(); // - ! toReturn = _nextObject; _iVeReadMyValue[subscriber].Set(); return toReturn; } private IEnumerable<T> GetCloneInstance(string key) { T res; do { res = GetCurrent(key); yield return res; } while (true); }
WaitAll()
method WaitAll()
: it supports the operation with no more than 64 instances at a time. But I need 100+ readers! How to be? Yes, in general, simple. And I began to clone clones. Out of every 64 “honest” clones, I choose a victim, which I will clone in the future. For a large number of readers I can have clones in 2, 3, 4, etc. generation! As the tests showed, quite viable creatures. It looks like this: private int _maxCloneOfOne = 64; // private IEnumerable<T> _input; // private Stack<ICloner<T>> cloners; // -, 64 private Dictionary<Guid, IEnumerable<T>> clones; // private Stack<IEnumerable<T>> missMe; // , - , public IEnumerable<T> GetClone() { if (cloners.Count == 0) cloners.Push(new Cloner<T>(_input)); // var isLast = clones.Count > 0 && clones.Count % (_maxCloneOfOne - 1) == 0; // - ICloner<T> cloner; var g = Guid.NewGuid(); IEnumerable<T> result; if (!isLast) { cloner = cloners.Peek(); // - } else { // - var lastCloneForCloner = cloners.Peek().GetClone(); missMe.Push(lastCloneForCloner); cloners.Push(cloner = new Cloner<T>(lastCloneForCloner)); g = Guid.NewGuid(); } result = cloner.GetClone(); clones.Add(g, result); return result; }
Source: https://habr.com/ru/post/165449/
All Articles