Делайте Legacy VB6 Timer Ticks или пропускайте, если предыдущий тик все еще работает

У нас есть (очень) устаревшее приложение, написанное на VB6 (15 лет?).

Приложение содержит таймер с интервалом 300 мс. Sub вызывается, когда отметка таймера выполняет пакет кода, который общается с некоторыми серверами SQL, печатает некоторые метки и так далее.

Когда все работает нормально, этот Sub выполняется за 5 мс - 10 мс - т.е. до наступления следующего интервала таймера - но он также тратит 290 мс до следующего тика.

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

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

Мы запланировали переписать его в.net, используя многопоточность и фоновые рабочие потоки - это всего лишь краткосрочное исправление, которое мы рассматриваем.

5 ответов

Решение

Если таймер является компонентом графического интерфейса пользователя (т.е. не является таймером пула потоков) и запускается сообщениями WM_TIMER, то события "OnTimer" не могут "складываться". WM_TIMER фактически не поставлен в очередь в очередь сообщений Windows, он синтезируется, когда основной поток возвращается в очередь сообщений И истекает интервал таймера.

Это не так, как работают таймеры VB6, событие Tick может сработать только тогда, когда ваша программа бездействует и прекращает выполнение кода. Технический термин "снова прокачивает цикл сообщений". DoEvents прокачивает цикл сообщений. Это очень опасная функция, поскольку она не только отправляет события Tick таймеров, но и отправляет все события. Включая те, которые позволяют пользователю закрыть ваше окно или запустить функцию снова, пока она еще занята выполнением. Не используйте DoEvents, если вы не хотите жить опасно или тщательно понимать его последствия.

Ваше стремление сделать его в 300 раз быстрее и обречено. Для начала, вы не можете получить 1 миллисекундный таймер. Разрешение часов в Windows недостаточно высокое. По умолчанию он увеличивается на 64 раза в секунду. Поэтому наименьший интервал, который вы можете получить, составляет 16 миллисекунд. Во-вторых, вы просто не можете ожидать, что медленный код будет произвольно быстрее, и ни в коем случае не потому, что события Tick не складываются.

Вы можете попросить Windows увеличить разрешение часов, для этого требуется вызов timeBeginPeriod(). Это не то, что вы должны созерцать. Если это действительно сработает, вы обязательно получите посещение довольно скрещенного администратора dbase с тупым инструментом, когда вы нажимаете на этот сервер каждую миллисекунду.

Когда все работает нормально, этот Sub выполняется за 5 мс - 10 мс - т.е. до наступления следующего интервала таймера - но он также тратит 290 мс до следующего тика.

Это именно то, что вы настроили, если интервал времени составляет 300 мс. Он не тратит 290 мс, он ждет, пока не истечет 300 мс, прежде чем снова запустить событие Tick.

Если вы хотите, чтобы он выполнялся чаще, установите интервал времени в 1 мс, остановите таймер в начале события Tick и запустите его снова, когда вы закончите обработку. Таким образом, между операциями будет только 1 мс времени простоя.

Чтобы максимизировать скорость, с циклическим набором инструкций, удалите таймер все вместе и сделайте так, чтобы функция вызвала единицу в конце точки входа программы (Sub Main или Form_Load).

Внутри функции выполните цикл и используйте QueryPerformanceCounter для управления интервалом повторения. Таким образом вы устраняете издержки системы сообщений таймера и можете обойти минимальный интервал таймера, который существует с таймером. Добавьте Doevents один раз в верхней части цикла, чтобы цикл мог запускать другие события; и потребляет простой во время ожидания.

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

Private isRunning As Boolean

Private Sub Timer1_Tick()
    If Not isRunning Then
        isRunning = True
        'do stuff
        isRunning = False ' make sure this is set even in the event of an exception
    End If
End Sub

Однако, если вы находитесь внутри этого обработчика событий столько, сколько хотите, или настолько быстро, насколько это возможно, почти 100% времени, ваше приложение будет медленно реагировать на события пользовательского интерфейса или не отвечать на них. Если вы положите DoEvents внутри do stuff вы дадите пользовательскому интерфейсу возможность обрабатывать события, но события пользовательского интерфейса остановят выполнение внутри do stuff, Представьте себе перемещение окна и остановку выполнения... В этом случае вы, вероятно, захотите создать другой поток для выполнения работы вне потока пользовательского интерфейса, но удачи вам в VB6 (я слышал, что это не невозможно).

Другие вопросы по тегам