Механизм синхронизации наблюдаемого объекта

Давайте представим, что мы должны синхронизировать доступ на чтение / запись к общим ресурсам. Несколько потоков будут обращаться к этому ресурсу как при чтении, так и при записи (чаще всего для чтения, иногда для записи). Предположим также, что каждая запись всегда будет запускать операцию чтения (объект является наблюдаемым).

Для этого примера я представлю класс, подобный этому (простите синтаксис и стиль, это просто для иллюстрации):

class Container {
    public ObservableCollection<Operand> Operands;
    public ObservableCollection<Result> Results;
}

Я испытываю желание использоватьReadWriterLockSlim для этого, кроме того, я бы поставил его наContainer уровень (представьте, что объект не так прост, и одна операция чтения / записи может включать несколько объектов):

public ReadWriterLockSlim Lock;

РеализацияOperand а такжеResult не имеет значения для этого примера. Теперь давайте представим некоторый код, который наблюдаетOperands и даст результат, чтобы положить вResults:

void AddNewOperand(Operand operand) {
    try {
        _container.Lock.EnterWriteLock();
        _container.Operands.Add(operand);
    }
    finally {
        _container.ExitReadLock();
    }
}

Наш гипотетический наблюдатель будет делать нечто подобное, но потреблять новый элемент, и он будет блокироватьсяEnterReadLock() чтобы получить операнды, а затемEnterWriteLock() чтобы добавить результат (позвольте мне пропустить код для этого). Это приведет к исключению из-за рекурсии, но если я установлюLockRecursionPolicy.SupportsRecursion тогда я просто открою свой код для взаимоблокировок (отMSDN):

По умолчанию новые экземпляры ReaderWriterLockSlim создаются с флагом LockRecursionPolicy.NoRecursion и не допускают рекурсию. Эта политика по умолчанию рекомендуется для всех новых разработок, потому чторекурсия вводит ненужные осложнения иделает ваш код более склонным к тупикам.

Я повторяю соответствующую часть для ясности:

Рекурсия [...] делает ваш код более склонным к тупикам.

Если я не ошибаюсь сLockRecursionPolicy.SupportsRecursion если из той же ветки я спрашиваю, скажем, читать блокировку тогдакто то else запрашивает блокировку записи, тогда у меня будет тупиковая блокировка, тогда то, что MSDN говорит, имеет смысл. Более того, рекурсия также может значительно измерить производительность (и это не то, чего я хочу, если я используюReadWriterLockSlim вместоReadWriterLock или жеMonitor).

Вопросов)

Наконец, мои вопросы (обратите внимание, я не ищу обсуждение общих механизмов синхронизации, я бы знал, что не так дляэтот сценарий производитель / наблюдаемый / наблюдатель):

Что лучше в этой ситуации? ИзбежатьReadWriterLockSlim в пользуMonitor (даже если в реальном мире чтения кода будет намного больше, чем записи)?Отказаться от такой грубой синхронизации? Это может даже привести к повышению производительности, но это сделает код намного более сложным (конечно, не в этом примере, а в реальном мире).Должен ли я просто сделать уведомления (из наблюдаемой коллекции) асинхронными?Что-то еще я не вижу?

Я знаю, что нетЛучший механизм синхронизации такорудие труда мы используем, должно быть правильно для нашего случая, но мне интересно, есть ли лучшая практика, или я просто игнорирую что-то очень важное между потоками и наблюдателями (представьте себе, чтобы использоватьРеактивные расширения Microsoft но вопрос общий, не привязанный к этим рамкам).

Возможные решения?

Я бы попробовал сделать события (как-то) отложенными:

1-е решение
Каждое изменение не сработаетCollectionChanged Событие хранится в очереди. Когда провайдер (объект, который отправляет данные) завершает работу, он вручную принудительно сбрасывает очередь (вызывая каждое событие последовательно). Это может быть сделано в другом потоке или даже в потоке вызывающего (но вне блокировки).

Это может работать, но это сделает все менее «автоматическим» (каждое уведомление об изменении должно быть вручную инициировано самим производителем, больше кода для записи, больше ошибок вокруг).

2-е решение
Другим решением может быть предоставление ссылки на нашзамок в наблюдаемую коллекцию. Если я завернуReadWriterLockSlim в пользовательском объекте (полезно скрыть его в удобном для использованияIDisposable объект) я могу добавитьManualResetEvent чтобы уведомить, что все блокировки были сняты, таким образом, сама коллекция может вызывать события (снова в том же потоке или в другом потоке).

3-е решение
Другая идея может заключаться в том, чтобы просто сделать события асинхронными. Если обработчику событий понадобится блокировка, то он будет остановлен, чтобы подождать, пока это время Для этого я беспокоюсь о большом количестве потоков, которые могут быть использованы (особенно если из пула потоков).

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

Ответы на вопрос(0)

Ваш ответ на вопрос