Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Если есть сеть объектов, которые уведомляют друг друга при помощи событий, ЭФ>то как всем этим управляют? Дело в том, что если возникает одно событие, ЭФ>оно при распространении генерирует вторичные события в сети объектов, ЭФ>те события в свою очередь генерируют новые события и всё это размножается до невозможности.
ЭФ>Если для просто объектов в памяти придумали сборку мусора, то как поступают с событиями?
Ну, вообще говоря, возможны два варианта:
1. Граф порождения событий ацикличен — тогда бесконечный рост невозможен. Рано или поздно все события дойдут до конца своих цепочек.
2. В графе порождения событий есть циклы — тогда возникает бесконечный рост (или как минимум неубывание количества сообщений), и система затыкается.
Свойства графа — штука относительно статическая. Всё управление сводится к обнаружению и разрыванию циклов.
Обнаружение зависит от того, на чём строятся события. Может быть, у вас будет переполнение стека; может быть — out of memory, может быть — забивание очереди.
Способ разрыва цикла делается по-разному.
Для начала можно попытаться ввести классы событий, и сделать так, чтобы порождение событий обязано было менять "класс" только в одну сторону.
Ну, там — из обработчика события "файл изменился" мы можем породить событие "нужно добавить запись в лог", а вот из обработчика события "добавляем запись в лог" мы не имеем права порождать событие "файл изменился".
Иногда бывает так, что так сделать нельзя — изменения должны работать "в обе стороны". Например, в тех местах, где события делаются для синхронизации значений, обычно достаточно сделать проверку на изменение:
public void OnChangeX(int value)
{
if (x == value) return; // don't propagate a no-change!
x = value;
RaiseOnXChanged(x); // notify all the subscribers so they can reflect a new value of X.
}
Без первой строки у нас есть риск того, что кто-то из подписчиков поменяет своё значение, и сгенерирует новое OnChangeX(), свалив систему в бесконечный цикл. А при особенной удаче можно даже получить экспоненциальный рост количества необработанных сообщений.
Но такое работает в предположении однозначности устанавливаемого значения — мы легко отличаем ситуацию "уже обработано" от "ой, нет, ещё не обработано". Возможно, в цепочке будут противоречия — например, из-за округления.
Типа уменьшили ширину окна -> вложенные окна перестали влезать -> показали скролл-бар -> уменьшилась ширина окна -> одно из вложенных окон решило свернуться -> окна начали влезать -> убрали скролл-бар -> вложенное окно решило развернуться и т.п.
Для таких случаев лучше уходить от лэйаута на событиях к лэйаут менеджерам, которые видят одновременно всю "систему уравнений", и решают её без циклических согласований.
Для более общего случая, когда нет возможности отказаться от событий, и при этом мы не можем легко отличить "новое" событие от его "реплики", можно пользоваться различными способами.
Самый простой — TTL, как уже предложили. То есть после N итераций мы гарантированно прекращаем обработку события.
Достоинство — не надо выделять память под хранение предыстории. Недостаток — для маленьких N у нас есть риск не добраться до конца валидной цепочки сообщений; для больших — у нас всё ещё есть риск выполнить в N/2 (или N/3) больше работы, чем необходимо.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Эйнсток Файр, Вы писали:
S>> Свойства графа — штука относительно статическая.
ЭФ>Только граф событий не статический, и строится он поверх модели, которая тоже не статическая...
Ну, поэтому я и написал — относительно статический
Не видя конкретного примера, трудно угадать, какая доля связей известна заранее, а какая — возникает на лету.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
А вот зачем было вообще вводить в язык такой синтаксис? Тем более, что функции отдельно всё равно потом по-другому добавили (Func<TResult>).
Наверное же хотели как лучше.
А если признали, что вышло плохо, наверное надо весь UI переделать по-новому?
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>А вот зачем было вообще вводить в язык такой синтаксис? Тем более, что функции отдельно всё равно потом по-другому добавили (Func<TResult>). ЭФ>Наверное же хотели как лучше. ЭФ>А если признали, что вышло плохо, наверное надо весь UI переделать по-новому?
Вот сейчас не понял, о чём речь.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>А вот зачем было вообще вводить в язык такой синтаксис? Тем более, что функции отдельно всё равно потом по-другому добавили (Func<TResult>). ЭФ>Наверное же хотели как лучше.
Хотели, как быстрее. События (о них же речь) были пересены в первую версию C# по мотивам Delphi и MFC. Дженерики появились лишь во второй. Т.е. Func<TResult> стало возможно гораздо позже.
ЭФ>А если признали, что вышло плохо, наверное надо весь UI переделать по-новому?
Да не так уж и плохо вышло, если смотреть на другие мертвые переделки UI.
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Если есть сеть объектов, которые уведомляют друг друга при помощи событий, ЭФ>то как всем этим управляют? Дело в том, что если возникает одно событие, ЭФ>оно при распространении генерирует вторичные события в сети объектов, ЭФ>те события в свою очередь генерируют новые события и всё это размножается до невозможности
Это разветвители.
ЭФ>Если для просто объектов в памяти придумали сборку мусора, то как поступают с событиями?
Здравствуйте, Эйнсток Файр, Вы писали: ЭФ>Если для просто объектов в памяти придумали сборку мусора, то как поступают с событиями?
Очередь сообщений всегда имеет конечный размер. Когда количество сообщений превысит максимальный размер очереди, то положить новое сообщение в очередь станет невозможным. На читающей стороне можно читать и отрабатывать все сообщения разом, накопившиеся на данный момент времени, чтобы очередь сообщений опорожнялась полностью при чтении. Всё просто, ребята.