我正在尝试创建一种缓冲输入的形式,看看在不使用Rx或任何其他库(标准4.5之外)的情况下实现.net是多么容易。所以我想出了以下课程:
public class BufferedInput<T>
{
private Timer _timer;
private volatile Queue<T> _items = new Queue<T>();
public event EventHandler<BufferedEventArgs<T>> OnNext;
public BufferedInput() : this(TimeSpan.FromSeconds(1))
{
}
public BufferedInput(TimeSpan interval)
{
_timer = new Timer(OnTimerTick);
_timer.Change(interval, interval);
}
public void Add(T item)
{
_items.Enqueue(item);
}
private void OnTimerTick(object state)
{
#pragma warning disable 420
var bufferedItems = Interlocked.Exchange(ref _items, new Queue<T>());
var ev = OnNext;
if (ev != null)
{
ev(this, new BufferedEventArgs<T>(bufferedItems));
}
#pragma warning restore 420
}
}主要是,一旦计时器滴答作响,它就会切换队列并继续触发事件。我意识到这件事可以用一张清单来完成.
过了一会儿,我得到了以下熟悉的例外情况:
Collection was modified after the enumerator was instantiated.在以下几行:
public BufferedEventArgs(IEnumerable<T> items) : this(items.ToList())声明和测试程序如下:
public sealed class BufferedEventArgs<T> : EventArgs
{
private readonly ReadOnlyCollection<T> _items;
public ReadOnlyCollection<T> Items { get { return _items; } }
public BufferedEventArgs(IList<T> items)
{
_items = new ReadOnlyCollection<T>(items);
}
public BufferedEventArgs(IEnumerable<T> items) : this(items.ToList())
{
}
}
class Program
{
static void Main(string[] args)
{
var stop = false;
var bi = new BufferedInput<TestClass>();
bi.OnNext += (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.Items.Count + " " + DateTime.Now);
};
Task.Run(() =>
{
var id = 0;
unchecked
{
while (!stop)
{
bi.Add(new TestClass { Id = ++id });
}
}
});
Console.ReadKey();
stop = true;
}
}我的想法是,在调用Interlocked.Exchange (一个原子操作)之后,调用_items将返回新的集合。但似乎有一只绿色植物在工作..。
发布于 2014-10-16 21:56:56
调用Interlocked.Exchange (原子操作)后,对_items的调用将返回新集合
嗯,那是真的。但是_items的读取发生在调用Interlocked.Exchange之前。
这一行代码
_items.Enqueue(item);转化为多个MSIL指令,大致如下:
ldthis ; really ldarg.0
ldfld _items
ldloc item
callvirt Queue<T>::Enqueue如果InterlockedExchange发生在第二条指令和第四条指令之间,或者在执行Enqueue方法期间的任何时间,BAM!
https://stackoverflow.com/questions/26414567
复制相似问题