Таймер своими руками (обёртка к SetTimer)

Здесь можно найти готовые «кирпичики» — части кода, пригодные для построения более крупных проектов, а также решения различных типовых и не очень задач на VB.

Модератор: Brickgroup

Antonariy
Повелитель Internet Explorer
Повелитель Internet Explorer
Аватара пользователя
 
Сообщения: 4824
Зарегистрирован: 28.04.2005 (Чт) 14:33
Откуда: Мимо проходил

Сообщение Antonariy » 16.05.2008 (Пт) 14:11

Ну не выскакивает в exe системное сообщение об ошибке, хоть тресни.
И кстати я знаю почему. Перед вызовом ITimer.Tick стоит On Error Resume Next:
Код: Выделить всё
Private Sub TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal lngSysTime As Long)
Dim idCls As Integer, nTmr As Integer, popravka As Long
    For idCls = 0 To maxImpl
        For nTmr = 0 To allImpl(idCls).maxIndex
            If idEvent = allImpl(idCls).allTimers(nTmr).id Then
                On Error Resume Next
                If idCls Then allImpl(idCls).Timer.Tick nTmr, lngSysTime
                Exit Sub
            End If
        Next nTmr
    Next idCls
    KillTimer 0, idEvent
End Sub
Лучший способ понять что-то самому — объяснить это другому.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 16.05.2008 (Пт) 15:20

arthur2 писал(а):
Я считаю, что если я достаточно автиритетен для человека, что он мочла согласится с моим мнением, то доказательства не нужно писать.
Считай, кто ж тебе мешает. В программировании ты для меня несомненный авторитет. Но "не сотвори себе кумира"- гуру тоже ошибаются. И - как и простые смертные - они тоже не любят своих ошибок признавать, тем более когда были столь категоричны. Надувание щёк не убеждает - убеждают довыды.

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


arthur2 писал(а):И кстати, не твоё ли кредо - ни кому не верить на слово и до всего доходить самому?

Первое - нет. Второе - да.

Когда, например, я спрашивал tyomitch'а, обязаны ли быть блоки таблицы экспортов в одной секции и он сказал мне, что не обязаны, я не требовал подтверждения этих слов и не проводил экспериментов, чтобы выяснить, прав ли был он.


arthur2 писал(а): А я и в школе с преподами спорил, и в институте. Причём - вот совпадение - почему-то именно с теми, кто для меня был авторитетом. Странно, правда?

Да нет, не странно, наоборот - логично. Какой смысл спорить с человеком, который и мнение которого для тебя ничего не значат. (Утрируя) Ты же не пойдёшь спорить с необразованным бомжем, доказывая ему, что земля всё-таки круглая?


Таймер с интервалом 100 мс, открывает файл, читает первую половину файла, делает DoEvents, читает вторую половину, закрывает файл.
Пример какой-то вымученый. Ну ладно, в такой ситуации апи-таймер не годится, и что? А в другой - очень даже годится. Используя твою логику, и так любимый тобой язык метафор (в котором ты плаваешь), можно сказать, что "туфельки у вас дрянь, потому что в них по крышам лазить не удобно". Если поднапречься, можно, наверное, и для стандартного таймера придумать ситуацию, после которой винду переставлять придётся.


1) В такой ситуации API-таймер годится. В такой ситуации нужно просто отключение таймера на время обработки тика.
2) Определимся с терминологией. Таймер, который останавливается на время выполнения события, назовём "ждущим таймером", а таймер, который продолжает тикать во время выполнения события - соотвественно неждущим.

Я вижу, что сейчас у тебя сложилось мнение, что у каждого вида таймера есть свои преимущества и недостататки. Причём тебе кажется, что у неждущего таймера больше преимуществ чем недостатков.

Если я правильно понял, тебе считаешь что:
1) У ждущего таймера больше безопасность но меньше применимость.
2) У неждущего таймера безопасность меньше (см. пример), но больше применимость.

Твоя ошибка заключается в том, что ты считаешь безопасность и применимость равнозначными факторами. Однако это неравнозначные факторы. Если таймер не пользовяет сделать что-то, это можно обойти. Если же таймер позволяет сделать что-то, но в случайные моменты может порушить всю программу - это нельзя обойти.

Ты не понимаешь, что всякая неконтролируемая рекурсия (неконтролируемая рекурсия - такая рекурсия, о которой известно, что чёткого условия прекращения рекурсивной ветки фреймов вызова нет) -- зло.

Например рекурсия:
Код: Выделить всё
Public Sub A()
    If Rnd > 0.5 Then A
    Debug.Print "x";
End Sub

является неконтролируемой, т.к. её глубина носит случайный характер. Вполне возможно, что "повезёт так", что рекурсии хватит стека. А возможно и не хватит.

С твоим таймером так же. Будет ли рекурсия продолжаться будет зависить от того, успеет ли код до DoEvents до того момента, как произойдёт следующий тик таймера. Время выполнения кода так же носит случайный характер.

И в конце концов, приведите мне пример, где неждущий таймер действительно бы пригодился?


Antonariy
Зачем вообще пользоваться ручным COM'ом - чтобы не плодить лишние ссылки, которые потом может быть будет не очень удобно удалять.
.
Что значит не нужно плодить лишние ссылки? Ручной COM отличается от автоматического тем, что все вызовы AddRef, QueryInterface, Release и все операции с ссылками нужно будет делать ручками, в то время как всё это мог сделать за тебя компилятор.


Ведь так и знал, что придерешься
Не важно, что там хранится технически, важно, что без привязки к этим сущностям объект в vb существовать не может.

1) А я даже знал, что после придирки ты скажешь эту фразу.
2) Исключительно важно, потому что твои слова могут ввести читающих в заблуждение.

Малознакомых с COM заставит действительно поверить, что объекты хранятся в переменных, а при Set a = b - клонируются.

Более-менее знакомых (как arthur2) - заставит поверить, что действительно хранится ссылка на объект.
Тогда какой-нибудь чел подумает, что код

Dim a as Object
Dim b As Class1

Set b = new Class1
Set a = b
Set b = Nothing

Можно перевести в:
Код: Выделить всё
Dim a as Object
Dim b As Class1

Set b = new Class1
CopyMemory VarPtr(a), VarPtr(b), 4


Ведь, если верить твоим словам, в объектых переменных хранятся ссылки на объекты? Если я из одной переменной скопирую [ссылку на объект] в другую переменную, то ведь всё должно работать?




И потом пойдут топики "Кто-нибудь знает, почему этот код не работает?".
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Antonariy
Повелитель Internet Explorer
Повелитель Internet Explorer
Аватара пользователя
 
Сообщения: 4824
Зарегистрирован: 28.04.2005 (Чт) 14:33
Откуда: Мимо проходил

Сообщение Antonariy » 16.05.2008 (Пт) 16:16

Я не хочу убеждать кого-либо, когда пишу сообщение.
Многочисленные многобуквенные посты, отстаивающие твою точку зрения, заставляют думать об обратном.
Что значит не нужно плодить лишние ссылки?
Это значит, что ты опять поменял смысл собственного термина. В первый раз "ручной COM" ты отнес, как мне показалось, к ObjPtr/ObjFromPtr, о которых как раз шла речь (которые позволяют работать с объектом по адресу, а не по ссылке), а теперь к AddRef и т.д.
Лучший способ понять что-то самому — объяснить это другому.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 16.05.2008 (Пт) 22:21

Antonariy
Спасибо! Я там в общем немного не то спрашивал, но всё рано. стало понятно и то, что мне надо. А ещё - половина непоняток отпала у меня после того, как я ввёл в дебагер ?objptr(nothingh) :lol:

Хакер
Слушай, как поразительно легко ты умеешь перестроиться на нормальный конструктивный тон! Мне вон три раза пришлось чистить свой топик, прежде чем я избавил его от ёрничания по инерции.

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

Когда, например, я спрашивал tyomitch'а
Это немного другое. Вот если бы он сам сказал тебе про что-то в твоём коде, что там всё не так. Да ещё и не конкретно, а какой-нибудь метафорой? Конечно, ты бы прислушался к его мнению, но, думаю, всё-таки и проверил, и поэксперементировал бы.

1) В такой ситуации API-таймер годится. В такой ситуации нужно просто отключение таймера на время обработки тика.
Вот этим как раз таймер в моей обёртке (да и без обёртки) отличается от стандартного - он не ждущий и не неждущий, он такой, какой мне нужно в конкретной ситуации. Нужно, чтобы приостанавливался - пожалуйста, прямо в тике и приостанавливаю. А в конце тика снова запускаю. Нужно, чтобы с ритма не сбивался - не приостанавливаю.

Кстати, в ХТаймере пережидание тика устроено вовсе не временным выключением таймера, а как-то по-другому. Мне вообще кажется, что там нет такого кода, который бы преднамеренно устанавливал такую защиту. Всё-таки, по-моему, это вовсе не защита, а издержка реализации.

Про рекурсию: на сколько я знаком с термином, рекурсии там всё-таки не возникает. По крайней мере, в видимой части кода (что там за кулисами, даже предполагать не рискую). Следующий тик же вызывается не из предыдущего. И - повторяюсь - ситуация, где лично у меня какой-то из тиков обрабатывается дольше других, даже близко не предполагает, что хотя бы два тика подрят окажутся перекрывающимися. Но даже если и так - я же говорю, что такая длинная обработка происходит совсем не в каждом тике. И даже если три тика подряд перекроются (что практически невероятно), стек это как-нибудь переварит. Вон в моем тесте их 60 перекрывающихся (реально - значительно меньше, потому что по мере поступления новых старые тики всё-таки завершаются. И завершаются полностью, а не возвращением в саму себя, как при рекурсии).

Совсем другое дело, если следующий тик исходит из того, что предыдущий уже дообрабатывался и лезет куда-нибудь туда, где предыдущий всё ещё копается. Тут-то и может возникнуть неконтролируемая рекурсия (надеюсь, я правильно понял, в чём уязвимость?)

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

Ещё раз - мой таймер может быть как ждущим, так и не ждущим. У стандартного таких вариантов нет.

И в конце концов, приведите мне пример, где неждущий таймер действительно бы пригодился?
Приводил же. Везде, где важен ритм. В метрономе, например. А ещё - он просто удобней во многих ситуациях, где, собственно, ни какой реальной опасности не предвидится.

а при Set a = b - клонируются.
:lol: В точку! я так и думал года три назад.

заставит поверить, что <в переменных> действительно хранится ссылка на объект.
А как на самом деле, а то ведь так и думал?
Артур
 
   

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 17.05.2008 (Сб) 0:40

Кстати, ещё раз про XTimer - там я нашёл ещё одну неприятную вещь. Стандартный таймер логичен - новый интервал он начинает отсчитываться акурат после завершения обработки тика. То есть, если вызывать msgbox-ы с интервалом в секунду, то следующий бокс появится как раз через секунду после того, как закрылся предыдущий.

А вот в XTimer второй бокс появляется после первого через непредсказуемый промежуток времени. Что и вовсе фи.

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

А ещё там есть флаг в TimeProc, который выставляется, когда начинается обработка тика, и снимается после. А если флаг уже установлен - выход из процедуры. Очень похоже, что это и есть та самая защита? Нифига. Я закомментировал эти флаги, но поведение таймера не изменилось.

Ещё там есть if err.number then killtimer, но и без этого заметных изменений в поведении таймера не произошло.

Моё предположение: класс в принципе не может сгенерить следующее событие, если не обработалось предыдущее. Именно поэтом реализация обёртки через класс и withevents приводит к нестандартному дла апи-таймера поведению.

Implements рулит!
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 17.05.2008 (Сб) 3:58

Многочисленные многобуквенные посты, отстаивающие твою точку зрения, заставляют думать об обратном.

arthur2 имел ввиду посты, когда я пишу "Этот способ плохой. Не используй его." и ухожу. И я имел ввиду это же.

Это значит, что ты опять поменял смысл собственного термина. В первый раз "ручной COM" ты отнес, как мне показалось, к ObjPtr/ObjFromPtr, о которых как раз шла речь (которые позволяют работать с объектом по адресу, а не по ссылке), а теперь к AddRef и т.д.

Это неразрывные вещи. Если ты поменял FOO as object на FOO as long, ты ты будешь вынужден юзать и ObjPtr/ObjFromPtr и AddRef/Release/QueryInterface. Если от ObjPtr/ObjFromPtr ты ещё можешь отказаться (в пользу GetMem4 или CopyMemory, хотя "ручной COM" от этого не станет менее ручным), то AddRef и Release для своей FOO as long придётся делать хоть как.

Вот я и спрашивал, какой смысл менять Object на Long, и писать тот код, который могу бы за тебя сгенерировать VB? (Учитывая что он сгенерирует более компактный и быстрый код, чем ты)
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Antonariy
Повелитель Internet Explorer
Повелитель Internet Explorer
Аватара пользователя
 
Сообщения: 4824
Зарегистрирован: 28.04.2005 (Чт) 14:33
Откуда: Мимо проходил

Сообщение Antonariy » 17.05.2008 (Сб) 9:41

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

В случае с XTimer (в оригинале) - ссылка на него хранится там, где он объявлен, а так же записывается в массив в модуле при инициализации. Поэтому Set Timer = Nothing, сделанное в области объявления, таймер не уничтожает. Заменив в массиве ссылку на указатель, я добился того, что ссылка на XTimer осталась всего одна - в области объявления, поэтому таймер тоже стал уничтожаться при ее уничтожении.

Насколько все было бы проще, если бы ты просто сразу посмотрел кирпич и мой пример, а не полагался на свою так называемую "телепатию". :(
Лучший способ понять что-то самому — объяснить это другому.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 17.05.2008 (Сб) 10:25

В случае с XTimer (в оригинале) - ссылка на него хранится там, где он объявлен, а так же записывается в массив в модуле при инициализации. Поэтому Set Timer = Nothing, сделанное в области объявления, таймер не уничтожает. Заменив в массиве ссылку на указатель, я добился того, что ссылка на XTimer осталась всего одна - в области объявления, поэтому таймер тоже стал уничтожаться при ее уничтожении.

Заменив в массива ссылку на указатель, тебе пришлось бы вызывать AddRef/Release для этой ссылки (что являлось бы "ручным" COM-ом). Но так как ты говоришь, что "ссылка осталась всего одна" - я могу сделать вывод, что ты забил на AddRef/Release. А так делать -- вообще жутко неправильно. Это нарушение базовой концепции COM-а. По сути, ты получаешь массив указателей и совершенно не знаешь, до каких пор в том месте, куда указывают поинтеры, всё ещё те данные, что нужны тебе. Это даже хуже, чем нулевые указатели. Если нулевой указатель можно хотя бы проверить (не обращаться к данным по нему, если он == 0), то каким образом ты собрался определять, жив ли ещё объект*? Отличный механизм определения жив объект или нет заложен в основы COM - это подсчёт ссылок. Но ты от него отказался. Ты можешь, по прежнему используя массив ссылок, воспользоваться подсчётом ссылок - но тогда ты будешь делать то же самое, что сделалось бы само при использовании объектных переменных.

* Я допускаю, что способ определить, жив объект или нет, ты вполне способен реализовать. Например, ты можешь в классе завести свойство, которое будет хранить индекс элемента массива указателей, а в Class_Terminate этот элемент обнулять. Но тогда тебе при удалении одного элемента из массива указателей придётся пройтись по всем классам и декрементировать в них индекс элемента массива указателей. Неужели всё это лучше, чем не нарушать COM, а ввести специальный метод UnloadTimer?

По поводу того, что я не скачал примеры:
1) Я уже несколько суток пордряд качаю очень большой файл в 10 потоков. И страница просмотра топика грузится у меня минуты 3.
2) Я не хотел смотреть примеры по двум причинам, одна из которых -- нехватка времени.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Antonariy
Повелитель Internet Explorer
Повелитель Internet Explorer
Аватара пользователя
 
Сообщения: 4824
Зарегистрирован: 28.04.2005 (Чт) 14:33
Откуда: Мимо проходил

Сообщение Antonariy » 17.05.2008 (Сб) 16:00

Если нулевой указатель можно хотя бы проверить (не обращаться к данным по нему, если он == 0), то каким образом ты собрался определять, жив ли ещё объект*?
Я об этом знаю и строю архитектуру так, что просто некому будет обращаться к несуществующему объекту. Процедура в модуле жестко связываетя с экземпляром объекта - в XTimer TimerProc прибивается при Class_Terminate и никакой другой ее "экземпляр" по конкретно этот экземпляр объекта не знает.

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

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 17.05.2008 (Сб) 16:49

Итак, я проверил. Как я и говорил, ни какая это не защита, а просто свойство реализации. Класс - любой класс - не генерит следующее событие, если предыдущее не завершилось.

В стандартном таймере, вероятно, таймер именно что приостанавливается. А вот в ХТаймере события вовсе не приостанавливаются, но просто не доходят до класса, продолжая тикать за кулисами.

Что в итоге:
1. Стандартный таймер. Интервал отсчитывается от конца обработки предыдущего события.

2. ХТаймер. Интервал отсчитывается от начала предыдущего события. Так что ритм он держать всё же может. Но перекрывающиеся события в нём не допускаются (причём, не специально, а в следствии особенности реализации)

3. Апи-таймер (без обёртки) Допускает перекрытие нескольких тиков.

4. Моя обёртка универсальна. Может вести себя хоть так, хоть эдак - как нужно тому классу, который её использует, так и будет:
    а) как стандартный таймер - в тике нужно остановить таймер, в конце тика снова запустить.
    б) как ХTimer - в тике нужно завести статическую переменную-флаг, устанавливать её на входе и сбрасывать на выходе. Если на входе флаг уже стоит, выходить сразу. (Забавно, что именно такой подход и предусмотрен в ХТаймере, но он там не нужен - особенность реализации приводит к тому, что и без флага таймер ведёт себя, как описано)
    в) как апи-таймер (несколько событий подряд могут перекрываться).


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

Ну что, кажется, вопрос решён. Спор можно закрыть?
Артур
 
   

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 17.05.2008 (Сб) 17:11

Класс - любой класс - не генерит следующее событие, если предыдущее не завершилось.
Вот это точно фигня
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 17.05.2008 (Сб) 17:29

Вот этим как раз таймер в моей обёртке (да и без обёртки) отличается от стандартного - он не ждущий и не неждущий, он такой, какой мне нужно в конкретной ситуации. Нужно, чтобы приостанавливался - пожалуйста, прямо в тике и приостанавливаю. А в конце тика снова запускаю. Нужно, чтобы с ритма не сбивался - не приостанавливаю.

Я разве говорил, что твой таймер плохой? Я говорил, что неждущий таймер не круче ждущего, а намного опаснее его.

Поэтому, я считал более рациональным изменить принцип и делать таймер не приостанавливаемым в обработчки тика, а возобновляемым.

Совсем другое дело, если следующий тик исходит из того, что предыдущий уже дообрабатывался и лезет куда-нибудь туда, где предыдущий всё ещё копается. Тут-то и может возникнуть неконтролируемая рекурсия (надеюсь, я правильно понял, в чём уязвимость?)

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


Я не могу понять тебя.

Отличие неждущего таймера от ждущего в том, что пока обрабатывается тик, неждущий продолжает выбрасывать тик-эйвенты.
(Так?)

Единственный способ обработать эти тик-эйвенты (тебе ведь надо их обработать, иначе зачем их генерировать если их никто не работает?), это вызвать DoEvents в работающем обработчике тика.
(Так?)

Если вызвать DoEvents в работающем обработчике тика, DoEvents достанет из очереди сообщений WM_TIMER, передаст его TimerProc-у, и дальше возникнет новый тик, и будет запущено обработчик нового тика, и в нём опять будет вызван DoEvents?

И будет рекурсия. Если вызывать DoEvents. А если не вызывать, толку от неждучести таймера нет, её просто не видно.

Приводил же. Везде, где важен ритм. В метрономе, например.
IBM PC не является компьютером реального времени. Точный ритм не получишь никак. К тому же, причём здесь точный ритм? У тебя что, ритм сбивался из-за того что обработчик тика работал слишком долго?

А ещё - он просто удобней во многих ситуациях, где, собственно, ни какой реальной опасности не предвидится.

Начинающие и малоопытные программисты вообще не предвидят нигде появление опасности. А она, тем не менее, появляется и проявляет себя во всей красе.

заставит поверить, что <в переменных> действительно хранится ссылка на объект.

А как на самом деле, а то ведь так и думал?


Смотри аттач. Только обрати внимание на размер Immediate Pane.
Вложения
disput_2arthur2.rar
(3.54 Кб) Скачиваний: 218
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 18.05.2008 (Вс) 13:16

Хакер
Мне кажется, мы уже практически полностью сошлись во мнении. Да, ты давно убедил меня, что с неждущем таймером нужна бОльшая осторожность. Но то, что его всё же можно использовать, ты вроде бы тоже уже не возражаешь.

Если вызвать DoEvents в работающем обработчике тика, DoEvents достанет из очереди сообщений WM_TIMER, передаст его TimerProc-у, и дальше возникнет новый тик, и будет запущено обработчик нового тика, и в нём опять будет вызван DoEvents?
Нет. В обработчике стоит какое-то условие, и лишь когда оно наступает, есть необходимость делать что-то более длинное, чем обычно. Но и эта более длинная обработка вряд ли не уложится в интервал. Зато она этот интервал не увеличит - следующий тик наступит вовремя. Крайний случай - обработка почему-то затянулась, два события подряд всё-же перекрываются, дуэвент срабатывает. Рекурсия в два вложения - не очень страшно, вполне контролируемо. Три события подряд перекрываются - уже практически невероятно. Но и три вложения - вполне контролируемая рекурсия.

Я даже специально не могу придумать ситуацию, в которой рекурсия начнёт неконтролируемо разрастаться.

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

А если не вызывать <doevent>, толку от неждучести таймера нет, её просто не видно.
Толк есть. Он в том, что в этом случае, если обработка не была дольше интервала, событие наступит вовремя. А если была, то хотя бы не через ещё один интервал (как у стандартного таймера).

Точный ритм не получишь никак. К тому же, причём здесь точный ритм? У тебя что, ритм сбивался из-за того что обработчик тика работал слишком долго?
В стандартном таймере - однозначно сбивается. В моём - практически нет.

То, что точный ритм не получится - я знаю. Увы. Но хотелось бы добиться хотя бы более-менее точного. Поэтому сделал лично для себя у таймера дополнительное свойство - самоуточняемость. Алгоритм такой (описываю для интервала в 1000:
1. При запуске таймера запоминаю системное время плюс 1000
2. Реальный интервал ставлю в 750
3. В следующем TimeProc интервал перевожу в 50 (около реальной точности, на сколько я понимаю)
4. Проверяю, а не стало ли ситсемное время больше, чем запомненное.
5. Если не стало, ни чего не делаю
6. Если стало, снова перевожу интервал в 750, прибавляю к запомненному 1000, генерирую событие.
7. Перехожу к пункту 3.
(проверка на то, а не обнулялось ли системное время, есть - опустил, чтобы не усложнять описание)

На моей машине это, в общем-то не нужно - и обычный апи-таймер без ухищрений более-менее хорошо держит ритм. А вот на старой (второй пень с вин-98 ) - очень даже нужно, там за минуту даже апи-таймер и даже без нагрузки системы отстаёт на 200

Смотри аттач.
Спасибо! Очень доходчиво. (правда, это как раз тот случай, когда я бы и на слово поверил, но с примером гораздо круче. Главное, что я не только поверил, но и понял.) В порядке любопытства - а указатели именно на объекты вообще бывают?

ANDLL

Вот это точно фигня

Может и фигня, но это я ни где не вычитал и сам не выдумал, а просто попробовал. См. вложение. Может, я не правильно себе это объяснил, но это вовсе не отменяет того, что поведение таймера, реализованного через класс, именно такое.
Вложения
for ANDLL.zip
Ответ для ANDLL
(2.09 Кб) Скачиваний: 224
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.05.2008 (Вс) 13:40

Нет. В обработчике стоит какое-то условие, и лишь когда оно наступает, есть необходимость делать что-то более длинное, чем обычно. Но и эта более длинная обработка вряд ли не уложится в интервал. Зато она этот интервал не увеличит - следующий тик наступит вовремя.

Если смысл в том, что "следующий тик наступит вовремя", то это преимущество не [неждущего таймера], а таймера, у которого отчёт начинается не от конца обработки тика, а от предыдущего тика. Такой таймер вполне может быть ждущим.

Хочу пример применения неждущего таймера.

З.Ы. "условие" не "наступает", оно "становистя истинным". Условие, это, вобщем-то, выражение, такое же как 2+2*7, и оно по таким же правилам вычисляется, и когда результат выражения равен True, тогда оно считается истинным.

Крайний случай - обработка почему-то затянулась, два события подряд всё-же перекрываются, дуэвент срабатывает. Рекурсия в два вложения - не очень страшно, вполне контролируемо. Три события подряд перекрываются - уже практически невероятно. Но и три вложения - вполне контролируемая рекурсия.

DoEvents не может срабатывать, это не событие, чтобы срабатывать.
Рекурсия сама по себе страшна. Если рассмотреть тот же пример, то если в обработчике тика открывается файл, то в случае с рекурсией ты получишь File already open.


Толк есть. Он в том, что в этом случае, если обработка не была дольше интервала, событие наступит вовремя. А если была, то хотя бы не через ещё один интервал (как у стандартного таймера).

Опять же, повторюсь, это уже не следствие "неждучести" таймера, а следствие "отсчёта интервала относительно предыдущего тика".

Неждучестью таймера можно воспользоваться только с помощью DoEvents, и обязательно породить этим рекурсию. Это по определению неждущего таймера.

(Ну и слова мы тут выдумали :lol: ).

В порядке любопытства - а указатели именно на объекты вообще бывают?

Смотря что понимать под словом "объект". Вообще в COM приятно считать указателем на объект указатель на интерфейс IUnknown.

Потому как этот интерфейс поддерживают все без исключения COM-классы.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 18.05.2008 (Вс) 16:11

а таймера, у которого отчёт начинается не от конца обработки тика, а от предыдущего тика. Такой таймер вполне может быть ждущим.
Ну дык и я об этом - апи-таймер считает от начала предыдущего события, а стандартный - от конца. Да, неждучесть здесь не обязательна (это я проглядел), но и ждучесть не нужна. Если дуэвента нет, то ждучесть получится сама собой.

DoEvents не может срабатывать, это не событие, чтобы срабатывать.
Я, в отличие от многих, давно не считаю всё это твоими предирками. Но здесь я просто сформулировал так, как пришло в голову. Не вполне корректно, но ты же меня понял? Я имел ввиду, что срабатывает та строка кода, в которой дуэвент. Срабатывает - не в смысле что только здесь выполняется, а в смысле, что происходит то, ради чего он сюда воткнут.

File already open.
Ну так я и говорю - уязвимость проявляется тогда, когда второй тик думает, что первый уже доработал, а первый всё ещё работает. Если работа второго тика никак не зависит от того, закончилась ли работа первого, ни чего страшного не произойдёт.

Сам же писал - неконтролируемая рекурсия зло. Не любая. Хотя любая - требует осторожности, согласен. Хорошо, можно сделать ждучесть свойством по умлочанию, а неждучесть - включать при надобности. Зачем вообще исключать какую-либо возможность?

Неждучестью таймера можно воспользоваться только с помощью DoEvents
Не только. Примеры уже были: несколько msgbox-ов подряд. Через такой же таймер можно найти окно бокса и что-нить там поменять, например. Или погасить его через какой-то промежуток времени. Найти кнопку Отмена и влепить в неё тикалку секунд до закрытия. (Всё это можно сделать и как-нибудь по-другому, но почему бы не так? если так удобней)

Да мало ли что ещё - если есть свойство, всегда можно найти, как им воспользоваться. В отличии от ситуации, когда такого свойства просто нет. Нельзя же совсем отказаться от апи, толко потому что "малоопытные программисты не предвидят нигде появление опасности"

Вопрос: как именно организовать ждучесть в апи-таймере? Остановкой и запуском нельзя (теряем равномерность). Пропуском тиков на время занятости (как при реализации через класс) нельзя - получаем непредсказуемость следующего интервала.
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.05.2008 (Вс) 16:17

Не только. Примеры уже были: несколько msgbox-ов подряд.

MsgBox создаёт своё окно и передаёт управление message-loop'у. DoEvents просто передаёт управление message-loop'у.

Пропуском тиков на время занятости (как при реализации через класс) нельзя - получаем непредсказуемость следующего интервала.

Почему мы её получаем? Мы её не должны получать. Виндовый таймер ведь не знает (и не как не может узнать) обработали ли мы тик в TimerProc-е, или TimerProc был пустым и никакой обработки не было.

Значит, причина в другом.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 18.05.2008 (Вс) 17:04

Почему мы её получаем? Мы её не должны получать

Установлен интервал в две секунды. Вызываем из события msgbox. Если бокс провисит десять секунд - следующее событие наступит сразу по закрытии бокса. А если через десять секунд и 5 миллисекунд, то через две секунды после закрытия бокса.
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.05.2008 (Вс) 17:07

Не через две а через 995 мс (в идеале)
Всё логично. А как должно быть?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 18.05.2008 (Вс) 18:06

Может и фигня, но это я ни где не вычитал и сам не выдумал, а просто попробовал. См. вложение. Может, я не правильно себе это объяснил, но это вовсе не отменяет того, что поведение таймера, реализованного через класс, именно такое.
Теперь скомпилируй и запусти
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 18.05.2008 (Вс) 18:32

ANDLL
Спасибо! Понятно. В таком случае недостаток этой реализации - неудобство в отладке. А как объясняется-то?
Последний раз редактировалось arthur2 18.05.2008 (Вс) 19:10, всего редактировалось 1 раз.
Артур
 
   

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 18.05.2008 (Вс) 18:56

Хакер
Не через две а через 995 мс (в идеале)
Это ты зачем поправил? В таком случае, кстати, 1995 :lol:
А вообще - если уж подходить дотошно, 1995 это в миллисекунда, а 2 - в секундах. А если учитывать плюс-минус точность таймера?

Всё логично. А как должно быть?
Логично и должно быть, чтобы если обработка задержалась на дольше, чем интервал, следующее событие наступало сразу.

Можно проверять на выходе, сколько длилась обработка, и если дольше интервала, после дуевента генерить ещё одно событие... Но что-то мне подсказывает, что в этом способе будут подводные камни. Вылезит рекурсия, причём как раз неконтролируемая :lol:

А вообще неждучесть - это то, что уже есть, ждучесть - то, что нужно реализовывать. Для больших интервалов ждучесть вообще не нужна - случай, в котором проявится разница между ждущим и неждущим, почти невероятен. А для малых интервалов - вполне приемлема ждучесть, построенная на пропуске тиков.
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.05.2008 (Вс) 21:50

Логично и должно быть, чтобы если обработка задержалась на дольше, чем интервал, следующее событие наступало сразу.

Сразу после чего? После выхода из обработчика тика?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Сообщение arthur2 » 19.05.2008 (Пн) 9:19

После выхода из обработчика тика?
Какая разница, после чего? Тому, для кого тикает этот таймер, думаю, не важно, как это реализовано.

Ещё раз (рассматриваем ждучесть, построенную на пропуске тиков):

1. Интрервал, следующий после занятости, получается непредсказуемым:
    занятость короче интервала - всё нормально.
    занятость длиннее интервала - следующий интервал непредсказуем.
2. Для больших интервалов это недостаток. Но для больших интервалов ждучесть и не нужна. Там не возникает опасность, из-за которой мы её вводили.
3. Для маленьких интервалов непредсказуемость не заметна. Так что ждучесть на пропуске тиков вполне подходит.

Тем самым, я измеил своё мнение на: "...пропуском тиков - можно...".
Артур
 
   

Пред.

Вернуться в Кирпичный завод

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 6

    TopList