ThreadSafeObservableCollection из (Т)


Идея здесь состоит в том, чтобы реализовать простой, ориентирован на многопотоковое исполнение, наблюдаемой коллекции, чтобы клиенты могли связываться с ними, в то время как фоновые потоки могут обновить. Изменения в содержащихся в ней элементов поднять collectionchanged в случае.

Отзывы оценили:

public class ThreadSafeObservableCollection<T> : INotifyCollectionChanged
    where T: INotifyPropertyChanged
{
    private readonly Object _lock = new Object();
    private readonly Collection<T> _items = new Collection<T>();
    private ReadOnlyCollection<T> _readonlyItems;

    public ThreadSafeObservableCollection()
    {

    }

    public ThreadSafeObservableCollection(IEnumerable<T> items)
    {
        _items = new ObservableCollection<T>(items);
        foreach (T item in _items)
        {
            ((INotifyPropertyChanged) item).PropertyChanged += ItemPropertyChangedHandler;
        }  
    }

    public ReadOnlyCollection<T> Items
    {
        get { return _readonlyItems ?? (_readonlyItems = new ReadOnlyCollection<T>(_items)); }
    }

    public virtual void Add(T obj)
    {
        lock(_lock)
        {
            ((INotifyPropertyChanged) obj).PropertyChanged += ItemPropertyChangedHandler;
            _items.Add(obj);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, obj));
        }
    }

    public virtual void Remove(T obj)
    {
        lock (_lock)
        {
            ((INotifyPropertyChanged) obj).PropertyChanged -= ItemPropertyChangedHandler;
            _items.Remove(obj);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, obj));
        }
    }


    #region INotify Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
        var copy = CollectionChanged;
        if(copy != null)
        {
            CollectionChanged(this, args);
        }
    }

    private void ItemPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(args);
    }

    #endregion
}

Обновление

В ответ на комментарии о к readonlycollection, у меня есть переключатель его реализации на выход блока, который закрывается (с помощью ReaderWriterLock) для длины перечисления, например:

    public IEnumerable<T> Items
    {
        get
        {
            _lock.AcquireReaderLock(_lockTimeout);
            try
            {
                foreach (T item in _items)
                {
                    yield return item;
                }
            }
            finally
            {
                _lock.ReleaseReaderLock();
            }
        }
    }

Какие мысли?



3936
7
задан 19 марта 2011 в 05:03 Источник Поделиться
Комментарии
3 ответа

Ваши предметы инициализации я считаю, что это не ориентирован на многопотоковое исполнение. Используйте Ленивый для резьбы безопасная ленивая инициализация вместо.

На объект readonlycollection не потокобезопасными.


А объект readonlycollection может поддержать
несколько читателей одновременно, как долго
так как коллекция не изменяется.
Тем не менее, перечисление через
коллекции в действительности не является
потокобезопасной процедурой. Для того чтобы гарантировать
потокобезопасность при перечислении, вы
можно заблокировать коллекцию на
все время перечисления. Чтобы
коллекции для доступа нескольких
потоки для чтения и записи, вы
должен реализовать свой собственный
синхронизации.

Рассмотрите возможность использования ReaderWriterLock для поддержки одного писателя, но несколько читателей.


Кроме того, вы можете прочитать этот ответ, и в блоге упоминается там, где обсуждается , почему потокобезопасной коллекции так сложно, и не обязательно полезно.

6
ответ дан 19 марта 2011 в 05:03 Источник Поделиться

Функцию "Удалить", удаляет объект без проверки, если объект в коллекции.

Это вероятно, будет работать хорошо для большинства случаев, но было бы лучше, чтобы убедиться, что объект может быть удален до фактического изменения самого объекта и коллекции.

Что делать, если объект принадлежит к другой коллекции?

Этот код

((INotifyPropertyChanged) obj).PropertyChanged -= ItemPropertyChangedHandler; 

будет на самом деле удалить обработчик изменения от объекта, но не удалить объект из правильного списка.

Еще одна вещь, которую я заметил, был

var copy = CollectionChanged;
if(copy != null)

Сначала я, хотя вы сделали копию обработчика событий, чтобы убедиться, что другой поток не изменяя его между проверка на null и назвав его, но затем вы вызываете обработчик напрямую, так зачем делать копии на всех? (может быть, есть хорошая причина, я просто не знаю сам)

Иногда я нахожу его под рукой, чтобы объявить событие с пустой делегат, таким образом, я никогда не придется проверить на null перед вызовом его:

public event NotifyCollectionChangedEventHandler CollectionChanged = delegate {};

Я не уверен, если это делает код более медленный, так как это пустой метод будет вызываться всякий раз, когда событие происходит, но даже если это, в некоторых случаях его можно выключить.

2
ответ дан 19 марта 2011 в 06:03 Источник Поделиться

Как предупреждение, основная проблема с внедрением элементов заключается в том, что она позволяет абоненту полностью заблокировать доступ к не окончания итерации (т. е. блокировке). Вы не оставляете себя открытыми. Что может быть совершенно нормально, но вы должны быть осведомлены о проблемах, которые открывает ваш дизайн.

От Джона Скитс C# в глубину:


Стоит вспомнить, что большинство, наконец, блоки в коде не так явно написанные на C# - они генерируются компилятором как часть замка и с помощью инструкций. замок является особенно опасным в итератор блоков - в любое время у вас есть выход оператор return внутри блока блокировки, у вас есть проблема потоковой ждет. Ваш код будет удержать замок даже тогда, когда это дало следующее значение - и кто знает, как долго это будет, прежде чем клиент вызывает метод MoveNext() или Dispose()? Также любой конструкции try/finally блоки, которые используются для критически важных вопросов, таких как безопасность не должна появляться в итератор блоков: клиент может сознательно предотвратить окончательно блокировать выполнение, если они не нужны больше значения.

0
ответ дан 27 января 2012 в 10:01 Источник Поделиться