.Net PropertyGrid Потокобезопасность

У меня есть PropertyGrid в моем приложении, которое используется для редактирования произвольных объектов. Мне нужно иметь возможность запускать произвольную подпрограмму в другом потоке, который также просматривает эти объекты (функция поиска, если вам интересно). Очевидная проблема заключается в том, что пользователь может редактировать один из этих объектов в то время, когда мой поисковый поток читает его, чего было бы предпочтительнее избегать (хотя это, вероятно, не приведет ни к чему критичному, поскольку мой поисковый поток просто читает, не пишет).

Вызов lock(obj) достаточно прост из моей ветки поиска, но после просмотра документации и беглого просмотра кода PropertyDescriptorGridEntry в Reflector я не могу найти аналогичное место для использования вызова System.Threading.Monitor.Enter()/Exit() для рассматриваемого объекта в PropertyGrid. Я надеялся, что будут события BeginEdit и EndEdit, которые сделают это достаточно простым, но я не могу найти ничего подобного. Я бы предпочел не блокировать весь объект, пока он отображается в PropertyGrid, поскольку это, очевидно, заблокировало бы мой поток поиска, пока не будет выбран другой объект.

Я немного новичок в многопоточной модели Windows Forms, поэтому я надеюсь, что есть какой-то очевидный ответ, который я только что пропустил. Любая помощь?

Редактировать: Синхронное клонирование моих объектов перед запуском асинхронного поиска, вероятно, будет достаточно неэффективным, поэтому я мог бы также запустить сам поиск синхронно - смысл асинхронного запуска, конечно, в том, чтобы позволить моим пользователям продолжать работу. пока идет поиск. Поиск должен хорошо масштабироваться, так как набор данных, который я просматриваю, в конечном итоге окажется сколь угодно большим, из-за чего синхронное клонирование выглядит так, как будто оно вызовет проблему удобства использования, которую я пытаюсь избежать.


person Not Sure    schedule 01.04.2009    source источник


Ответы (4)


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

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

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

Таким образом, для свойства вы должны реализовать GetProperties для возврата конкретных подклассов PropertyDescriptor. Эти подклассы переопределяют методы GetValue и SetValue (и другие) и используют блокировку при доступе к этим переменным.

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

person casperOne    schedule 01.04.2009
comment
На самом деле, в случае с PropertyGrid вы могли бы сделать это проще, создав подкласс PropertyTab и переопределив GetProperties. По-прежнему много работы, но на самом деле вам не нужно использовать прокладку через ICustomTypeDescriptor (или TypeDescriptionProvider) - person Marc Gravell; 01.04.2009
comment
@Marc Gravell: вам все еще нужно создавать подклассы, производные от PropertyDescriptor, и делать то, что я указал, переопределяя GetValue и SetValue. Примерно столько же работы. Однако реализация ICustomTypeDescriptor имеет более широкое применение, поэтому я бы выбрал ее. - person casperOne; 01.04.2009
comment
Но с ICustomTypeDescriptor вам нужно управлять отображаемым типом, а не только сеткой. - person Marc Gravell; 02.04.2009
comment
@Marc Gravell: у вас нет особого контроля над сеткой, когда вы наследуете от PropertyTab. На основе PropertyTab вы должны вернуть PropertyDescriptors, которые являются просто метаданными, а не значениями. Вы по-прежнему должны блокировать доступ в том же месте, что и при реализации ICustomTypeDescriptor. - person casperOne; 02.04.2009
comment
@Marc Gravell: И в этом случае реализация ICustomTypeDescriptor не так уж сложна, вам просто нужно полагаться на TypeDescriptor. Сложная часть — это подкласс PropertyDescriptor. - person casperOne; 02.04.2009
comment
вздох к сожалению, это на самом деле кажется наиболее разумным решением. - person Not Sure; 02.04.2009

Это действительно не будет потокобезопасным. Вы можете маршалировать биты кода поиска в поток пользовательского интерфейса, но это замедлит работу и, возможно, сведет на нет смысл многопоточности...

Насколько быстрым должен быть поиск? Можно ли работать против клона? так далее

person Marc Gravell    schedule 01.04.2009
comment
Я знал, что не должен был вдаваться в подробности ;) - person Jon Skeet; 01.04.2009
comment
Отредактированный вопрос - синхронное клонирование, скорее всего, помешает цели потоковой передачи :( - person Not Sure; 02.04.2009

Можете ли вы клонировать данные перед их отображением и заставить поисковый поток работать с клоном? Если вы хотите, чтобы поток поиска «видел» изменения, вы можете реагировать на события в PropertyGrid и, возможно, каким-то образом вносить контролируемые изменения в клон. (Возможно, проще просто использовать «устаревшие» данные.)

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

person Jon Skeet    schedule 01.04.2009
comment
Отредактированный вопрос - синхронное клонирование, скорее всего, помешает цели потоковой передачи :( - person Not Sure; 02.04.2009

Я могу ошибаться, но мне кажется, что вы идете не с того конца.

Есть две разные проблемы, которые вам нужно решить.

Во-первых, убедитесь, что доступ к PropertyGrid осуществляется только в потоке пользовательского интерфейса. Если доступ к любому из его методов (включая методы получения/установки свойств) осуществляется из других потоков, вы будете страдать загадочным образом. Исключения, конечно, InvokeRequired() и Invoke.

Во-вторых, убедитесь, что ваша поисковая ветка работает правильно.

Чтобы решить первую проблему, либо убедитесь, что ваши объекты никогда не изменяются кроме потоком пользовательского интерфейса, или сделайте все ваши триггеры событий осведомленными о потоках так что события ваших объектов (например, PropertyChanged) всегда запускаются только в потоке пользовательского интерфейса.

Вторая проблема проще — пока ваш поисковый поток только ЧИТАЕТ из ваших основных объектов, все должно работать нормально. Да, ваша поисковая цепочка может непреднамеренно увидеть некоторые частично обновленные данные, но действительно ли это проблема?

Пара заключительных мыслей...

  • Не реализуйте свой поиск для итерации через саму PropertyGrid; получить список объектов заранее (они не обязательно должны быть клонами) и вместо этого работать с ним.

  • Рассматривали ли вы возможность использования Idle Processing для поиска? Объект Application запускает событие каждый раз, когда у приложения заканчиваются сообщения для обработки — вы можете подключиться к этому и выполнять 1 шаг поиска каждый раз, когда событие запускается. Что-то вроде многопоточности для бедняков, но без головной боли с мьютексом/блокировкой/семафоном. Я использовал это для очень хорошего эффекта в прошлом.

Обновление: если я правильно помню, PropertyGrid учитывает IEditableObject, вызывающий BeginEdit как только вы начнете изменять строку и EndEdit при переходе на другую строку. Вы можете использовать это, чтобы ваша поисковая цепочка не увидела неполные изменения.

Обновление 2. Далее я обнаружил то, что вы уже знали: PropertyGrid не поддерживает интерфейс IEditableObject.

У меня есть еще одно предложение — хотя это может потребовать больше усилий, чем вы хотите вложить.

Примерно в то время, когда пространство имен System.Transactions было представлено в .NET 2.0, я увидел статью об использовании внешней транзакции для обеспечения изоляции потоков для объектов. Идея состоит в том, что свойства вашего объекта имеют двойное хранилище. Во-первых, у вас есть зафиксированное значение, видимое для всех потоков, и у вас есть локальная переменная потока, используемая для хранения незафиксированных значений для каждого потока. Когда свойство изменяется, объект включается в любую внешнюю транзакцию, сохраняя новое значение в локальном потоке. Когда транзакция фиксируется или откатывается, значение для потока либо сохраняется, либо отбрасывается.

К сожалению, я не могу найти оригинальную статью, хотя кажется, что CSLA предоставляет эту поддержку. Надеюсь это поможет.

person Bevan    schedule 01.04.2009
comment
ваша поисковая цепочка может непреднамеренно увидеть некоторые частично обновленные данные — это единственная проблема, которая меня беспокоит и которую я бы предпочел избежать; ничто другое не является проблемой (я знаю, что делаю... в основном). Idle Processing мог бы сработать, если бы мое приложение не было таким сверхмощным. - person Not Sure; 02.04.2009
comment
Я не верю, что вы правильно вспоминаете - и ваши ссылки, и моя память указывают на то, что IEditableObject с BeginEdit и EndEdit используются DataGridView, а не PropertyGrid. - person Not Sure; 02.04.2009