有不同的方法可以自己实现,我决定使用
- 每个枚举器有一个专用线程,执行异步预缓冲
- 要预缓冲的固定数量的元素
这对于我手头的情况来说是完美的(只有几个,非常长时间运行的枚举器),但是如果你使用大量的枚举器,创建一个线程可能会太重,如果你使用固定数量的元素可能太不灵活需要更动态的东西,可能基于项目的实际内容。
到目前为止,我只测试了它的主要功能,可能还存在一些粗糙的边缘。它可以这样使用:
int bufferSize = 5;
IEnumerable<int> en = ...;
foreach (var item in new PreBufferingEnumerable<int>(en, bufferSize))
{
...
这是枚举器的要点:
class PreBufferingEnumerator<TItem> : IEnumerator<TItem>
{
private readonly IEnumerator<TItem> _underlying;
private readonly int _bufferSize;
private readonly Queue<TItem> _buffer;
private bool _done;
private bool _disposed;
public PreBufferingEnumerator(IEnumerator<TItem> underlying, int bufferSize)
{
_underlying = underlying;
_bufferSize = bufferSize;
_buffer = new Queue<TItem>();
Thread preBufferingThread = new Thread(PreBufferer) { Name = "PreBufferingEnumerator.PreBufferer", IsBackground = true };
preBufferingThread.Start();
}
private void PreBufferer()
{
while (true)
{
lock (_buffer)
{
while (_buffer.Count == _bufferSize && !_disposed)
Monitor.Wait(_buffer);
if (_disposed)
return;
}
if (!_underlying.MoveNext())
{
lock (_buffer)
_done = true;
return;
}
var current = _underlying.Current; // do outside lock, in case underlying enumerator does something inside get_Current()
lock (_buffer)
{
_buffer.Enqueue(current);
Monitor.Pulse(_buffer);
}
}
}
public bool MoveNext()
{
lock (_buffer)
{
while (_buffer.Count == 0 && !_done && !_disposed)
Monitor.Wait(_buffer);
if (_buffer.Count > 0)
{
Current = _buffer.Dequeue();
Monitor.Pulse(_buffer); // so PreBufferer thread can fetch more
return true;
}
return false; // _done || _disposed
}
}
public TItem Current { get; private set; }
public void Dispose()
{
lock (_buffer)
{
if (_disposed)
return;
_disposed = true;
_buffer.Clear();
Current = default(TItem);
Monitor.PulseAll(_buffer);
}
}