Привет всем!
Я тут работаю на BlackBox, пишу программу для наблюдения за температурой (условно говоря). Суть в том, что по COM-порту приходят данные, я их интерпретирую и добываю информацию о температуре и других параметрах системы управления отоплением дома. Для этого дела я написал подсистему "Mate", а данные отображаю на специализированных компонентиках (MateViews.View). На один параметр - один компонентик. Пользователь может расположить вьюшки в тексте или на форме, снабдив соответствующими комментариями, чтобы не забыть, где что отображается. Столкнулся с несколькими интересными проблемами, которыми и хочу поделиться.
Принципиальная особенность моей подсистемы в том, что компоненты в ней "активные". В том смысле, что они не ждут действий от пользователя, с ними и так все время что-то происходит. Даже если человек уйдет и сутки не будет появляться. Источником активности служит специальный Services.Action, который следит за поступлением новой информации из порта связи. При поступлении очередной порции данных рассылается уведомление о новом значении параметра и все компоненты, отображающие данный параметр, соответствующим образом обновляют свой внешний вид.
Такая взаимосвязь означает, что где-то обязательно должен существовать глобальный список компонентов для оповещения. И к этому списку должен иметь доступ тот самый экземпляр Services.Action, который инициирует рассылку сообщений. (Этот доступ не обязательно будет выражаться в прямой ссылке, но, тем не менее, в каком-то виде он должен быть налажен. Я сделал хук, благодаря чему работа с портом абсолютно не зависит от клиентского кода.)
Другой пример активного компонента - MateAutoSave.View - компонент для автоматического сохранения текущих значений параметров из какого-либо контейнера. Достаточно поместить такой компонент в один текст вместе с интересующими параметрами, и он будет запоминать текущее значение всех параметров через заданные интервалы времени. Потом по этим данным можно будет, например, построить графики, найти зависимости и вычислить настройки контура управления. Получается довольно просто и наглядно: берешь нужные параметры, помещаешь в один текст, туда же добавляешь компонент автосохранения, и все работает.
Если компонент из текста удалить, запись должна прекратиться. Или, например, если закрыть ненужное окно с сохраняемыми параметрами, запись тоже должна прекратиться, не так ли? И в самом деле, документ больше не открыт, глобальных ссылок на него и на компоненты, входящие в его состав, нет, значит, он будет уничтожен "сборщиком мусора", и запись данных прекратится...
Нет, к сожалению, это не так. Я столкнулся с тем, что запись продолжается. Более того, теперь ее невозможно остановить, поскольку после закрытия окна щелкнуть мышкой по компоненту для остановки записи уже не представляется возможным. Запись идет, растет объем используемой памяти. Остается только два варианта - перезапуск BlackBox или выгрузка модуля MateAutoSave (с парой сообщений об ошибках).
Используя знания, накопленные за годы работы в Delphi, я принялся искать какие-нибудь оповещения типа OnClose или OnDelete, которые бы позволяли отреагировать на закрытие документа или удаление компонента из контейнера, но ничего подобного не нашел. Постепенно во мне родилось понимание того, что объекты принципиально пассивны в том, что касается времени их жизни. Не только на уровне управления памятью (процедура NEW и сборщик мусора), но и на уровне фреймворка. Например, если объект удалили из документа (нажав клавишу BackSpace), то он ничего не узнает об этом. Он будет продолжать существовать как ни в чем ни бывало, ведь на него все еще имеется ссылка - в буфере "Undo"! И он будет считать, что находится в том же самом документе, из которого удален (переменная context не изменяется). А уж если на него есть глобальная ссылка, то он будет существовать до тех пор, пока не выгрузят модуль с этой ссылкой. Следовательно, будет существовать и весь документ, "удерживаемый" context'ом.
Что касается моих компонентов, то они "активны". Благодаря всем этим разбирательствам я понял принципиальную характеристику "активного компонента" - самостоятельное управление временем своей жизни. Активный компонент сам может решать, когда ему прекратить свое существование. А пока компонент активен, на него есть как минимум одна глобальная ссылка (в модуле Services).
А раз активные компоненты принципиально глобальны, необходимо вводить глобальные механизмы управления ими. Например, нужно в порождающем модуле иметь глобальный список активных на данный момент времени компонентов и процедуру останова, проходящую по списку и останавливающую все компоненты. Эта процедура пригодится на тот случай, если на активный компонент будут потеряны визуальные ссылки (например, если будет закрыто окно содержащего его документа). При остановке компонента он должен удаляться из глобального списка, при запуске - вновь добавляться в него.
Раз активный компонент сам управляет временем своей жизни, сборщик мусора не может его уничтожить. Чтобы дать работу сборщику мусора, такой компонент должен вовремя перейти в пассивное состояние. Но фреймворк не делает никаких подходящих оповещений для принятия такого решения. И, наверное, не может делать в принципе ввиду своей тотальной пассивности. В самом деле, ну и что, что окно документа закрыли? Сам документ-то может продолжать существовать благодаря глобальной ссылке на него. А окно можно и еще одно открыть.
Так что, как бы мне ни претила идея глобальных списков, мне без них не обойтись.
Пользуясь случаем, хочу передать привет тем форумчанам, которые утверждали, что без нитей (threads) невозможно нормально работать с COM-портами, сокетами и прочими последовательными коммуникациями. Работаю очень просто и без проблем. : )
|