2014-10-16 4 views
0

Я пытаюсь создать форму буферизованного ввода, чтобы увидеть, насколько легко было бы реализовать, без использования Rx или любой другой библиотеки (за пределами стандартного .net 4.5). Поэтому я придумал следующий класс:Interlocked.Exchange Collection Модифицированное исключение

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 вернет новую коллекцию. Но, похоже, к гномам в работах ...

+0

Does 'System.Collections.Concurrent.ConcurrentQueue ' count в качестве библиотеки? –

+0

Нет, пояснят в вопросе :) –

ответ

1

после вызова Interlocked.Exchange (атомная операция) имело место, вызов _items возвратит новую коллекцию

Ну, это правда. Но до Interlocked.Exchange произошло сообщение _items.

Эта строка кода

_items.Enqueue(item); 

превращается в несколько инструкций MSIL, грубо:

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

Если InterlockedExchange происходит между второй и четвертой инструкции, или в любое время во время выполнения метода Enqueue , БАМ!

+0

Хм, я предполагаю, что я пытаюсь сделать, просто невозможно без примитива? –

+0

@ Стюарт: Наверное, нет. У вас есть весь этот интервал, когда продюсер работает с очередью, что его нельзя прервать. ** Можно создать отдельную единую потребительскую очередь ** (я сделал это), но вы не сможете использовать для этого существующий класс «Queue». –

+0

можно ли это увидеть? На самом деле у меня действительно было это раньше, но я был на 50% медленнее, чем в очереди с блокировкой. Поэтому я не думаю, что я сделал что-то тихое (используя подход связанного списка, не пробовал метод на основе массива); –

 Смежные вопросы

  • Нет связанных вопросов^_^