0

Rxやその他のライブラリ(標準の.net 4.5以外)を使用せずに、実装がいかに簡単かを確認するために、バッファリングされた入力の形式を作成しようとしています。そこで、次のクラスを思いつきました。

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 への呼び出しは新しいコレクションを返します。しかし、作業中のグレムリンがあるようです...

4

1 に答える 1

1

Interlocked.Exchange への呼び出し (アトミック操作) が行われた後、_items への呼び出しは新しいコレクションを返します。

そうですね。しかし、 の読み取りは_itemsへの呼び出しの前に行われましたInterlocked.Exchange

このコード行

_items.Enqueue(item);

大まかに、複数の MSIL 命令に変換されます。

ldthis ; really ldarg.0
ldfld _items
ldloc item
callvirt Queue<T>::Enqueue

InterlockedExchange2 番目と 4 番目の命令の間、またはメソッドの実行中に発生するEnqueueと、BAM!

于 2014-10-16T21:56:56.200 に答える