首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Interlocked.Exchange集合修改异常

Interlocked.Exchange集合修改异常
EN

Stack Overflow用户
提问于 2014-10-16 21:54:03
回答 1查看 333关注 0票数 0

我正在尝试创建一种缓冲输入的形式,看看在不使用Rx或任何其他库(标准4.5之外)的情况下实现.net是多么容易。所以我想出了以下课程:

代码语言:javascript
复制
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
    }
}

主要是,一旦计时器滴答作响,它就会切换队列并继续触发事件。我意识到这件事可以用一张清单来完成.

过了一会儿,我得到了以下熟悉的例外情况:

代码语言:javascript
复制
Collection was modified after the enumerator was instantiated.

在以下几行:

代码语言:javascript
复制
public BufferedEventArgs(IEnumerable<T> items) : this(items.ToList())

声明和测试程序如下:

代码语言:javascript
复制
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将返回新的集合。但似乎有一只绿色植物在工作..。

EN

回答 1

Stack Overflow用户

发布于 2014-10-16 21:56:56

调用Interlocked.Exchange (原子操作)后,对_items的调用将返回新集合

嗯,那是真的。但是_items的读取发生在调用Interlocked.Exchange之前。

这一行代码

代码语言:javascript
复制
_items.Enqueue(item);

转化为多个MSIL指令,大致如下:

代码语言:javascript
复制
ldthis ; really ldarg.0
ldfld _items
ldloc item
callvirt Queue<T>::Enqueue

如果InterlockedExchange发生在第二条指令和第四条指令之间,或者在执行Enqueue方法期间的任何时间,BAM!

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26414567

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档