Возник вопрос: как правильно реализовывать вызов события (event raising)? Я так понимаю, что сам смысл "правильности" зависит от того, где мы вызываем событие. Если приложение однопоточное, то, очевидно достаточно формы InvokeEvent1. А вот с многопоточными приложениями не всё понятно.
Какой метод реализации выбрать? Есть ли рекомендации на этот счёт?
Здравствуйте, valker, Вы писали:
V>Здравствуйте!
V>Возник вопрос: как правильно реализовывать вызов события (event raising)? Я так понимаю, что сам смысл "правильности" зависит от того, где мы вызываем событие. Если приложение однопоточное, то, очевидно достаточно формы InvokeEvent1. А вот с многопоточными приложениями не всё понятно. V>Какой метод реализации выбрать? Есть ли рекомендации на этот счёт?
Экземпляры System.Delegate являются "неизменяемыми" поэтому, специальной синхронизации для них не требуется. Достаточно скопировать экзепляр, проверить копию на null и уже через нее сделать вызов.
Для подписки/отписки (реализация add/remove руками) синхронизация нужна. Стандартная реализация использует атрибут [MethodImpl(MethodImplOptions.Synchronized)] что, аналогично использованию lock (this) { ... }
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, TK, Вы писали:
TK>Здравствуйте, valker, Вы писали:
V>>Здравствуйте!
V>>Возник вопрос: как правильно реализовывать вызов события (event raising)? Я так понимаю, что сам смысл "правильности" зависит от того, где мы вызываем событие. Если приложение однопоточное, то, очевидно достаточно формы InvokeEvent1. А вот с многопоточными приложениями не всё понятно. V>>Какой метод реализации выбрать? Есть ли рекомендации на этот счёт?
TK>Экземпляры System.Delegate являются "неизменяемыми" поэтому, специальной синхронизации для них не требуется. Достаточно скопировать экзепляр, проверить копию на null и уже через нее сделать вызов.
TK>Для подписки/отписки (реализация add/remove руками) синхронизация нужна. Стандартная реализация использует атрибут [MethodImpl(MethodImplOptions.Synchronized)] что, аналогично использованию lock (this) { ... }
Programmers without a strong background in multithreaded programming may not immediately detect why this solution is wrong. Delegates are immutable reference types, so the local variable copy is atomic; no problem there. The problem exists in the memory model: it is possible that an out-of-date value for the delegate field is held in one processor's cache. Without going into a painful level of detail, in order to ensure that one is reading the current value of a non-volatile field, one must either issue a memory barrier or wrap the copy operation within a lock (and it must be the same lock acquired by the event add/remove methods).
Или же нам нужно "закладываться" на то, что подписчик может быть вызван после операции отписки?
Здравствуйте, valker, Вы писали:
V>Возник вопрос: как правильно реализовывать вызов события (event raising)? Я так понимаю, что сам смысл "правильности" зависит от того, где мы вызываем событие. Если приложение однопоточное, то, очевидно достаточно формы InvokeEvent1. А вот с многопоточными приложениями не всё понятно. V>Какой метод реализации выбрать? Есть ли рекомендации на этот счёт?
In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline:
lock (this) is a problem if the instance can be accessed publicly.
…
Если же подобных событий у класса несколько мне больше нравится подход с объявлением словаря подписчиков и хранения делегатов в нём, нежели использование многих [или даже нескольких] полей. Стандартным словарём является EventHandlerList. Примеры работы с ним описаны в статье How to: Handle Multiple Events Using Event Properties. Рихтер в одной из своих книг под .NET приводит свой класс на заданную тему EventHandlerSet. Сейчас вот удалось вот такую реализацию: A Truly Thread-Safe Event Handler Collection.
Самое удобное в подходе со словарями заключается в том, что поддержка многопоточности "спрятана" внутри словаря и указывается один раз при инициализации словаря. При всей остальной работе — в коде подписки\отписки\вызова заботится о синхронизации не нужно.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, valker, Вы писали:
V>Или же нам нужно "закладываться" на то, что подписчик может быть вызван после операции отписки?
Если подписчик подписывается на сообщения которые, генерируются "асинхронно" то, простой вариант — требовать, что подписчик должен игнорировать события пришедшие после отписки. Введение синхронизации в "генерацию" событий принесет больше проблем, чем потенциальной выгоды.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, valker, Вы писали:
V>Спасибо. Но я слышал вот здесь что:
ИМХО, у автора текста по ссылке некоторая каша в голове, хотя ключевые слова указаны правильные. Касательно же процитированного:
1) Кэш тут совершенно не при делах. Бо на стандартных архитектурах x86/x64 как минимум кэш данных когерентен на уровне железа, включая и мультисокетные конфигурации.
2) Проблема которую пытается описать автор не имеет никакого отношения к event'ам как таковым. Это проблема конкретного подписчика event'а.
V>Или же нам нужно "закладываться" на то, что подписчик может быть вызван после операции отписки?
Всё аналогично стандартным правилам для обычного многопоточного кода.
Если у нескольких потоков есть некоторое изменяемое общее (shared) состояние, то для работы с этим состоянием необходимо использовать какой-то из механизов синхронизации.
В нашем случае: есть поток исполняющий код подписчика, и есть поток модифицирующий event. Если подписчик использует некоторое изменяемое состояние связанное с содержимым event'а, то 100% надо думать о синхронизации. Если же такого состояния нет, то какая подписчику разница отписали его к моменту начала исполнения, или ещё не успели ???