Как сделать паузу на определенное количество времени? (Excel/VBA)
У меня есть лист Excel, который имеет следующий макрос. Я хотел бы повторять это каждую секунду, но опасно, если я смогу найти функцию для этого. Разве это не возможно?
Sub Macro1()
'
' Macro1 Macro
'
Do
Calculate
'Here I want to wait for one second
Loop
End Sub
16 ответов
Используйте метод Wait:
Application.Wait Now + #0:00:01#
или (для Excel 2010 и более поздних версий):
Application.Wait Now + #12:00:01 AM#
Добавьте это в свой модуль
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Или для 64-битных систем используйте:
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Назовите это в вашем макросе так:
Sub Macro1()
'
' Macro1 Macro
'
Do
Calculate
Sleep (1000) ' delay 1 second
Loop
End Sub
Вместо того, чтобы использовать:
Application.Wait(Now + #0:00:01#)
я предпочитаю:
Application.Wait(Now + TimeValue("00:00:01"))
потому что потом намного легче читать.
Это работает для меня безупречно. вставьте любой код до или после цикла "до". В вашем случае поместите 5 строк (time1= & time2= & do do) в конец цикла do
sub whatever()
Dim time1, time2
time1 = Now
time2 = Now + TimeValue("0:00:01")
Do Until time1 >= time2
DoEvents
time1 = Now()
Loop
End sub
Полное руководство по приостановке выполнения VBA
Исходная информация и объяснение
Все приложения Microsoft Office выполняют код VBA в том же потоке, что и основной пользовательский интерфейс. Это означает, что пока код VBA не вызывает , выполнение кода замораживает все приложение (оно будет отображаться как «не отвечающее» в диспетчере задач!). Сюда входят вызовы функции API. После вызова невозможно выйти из этого состояния, не дожидаясь истечения времени или принудительно закрывая Приложение и перезапуская его.
Специфичные для Excel Application.Wait
также страдает от этой проблемы, за исключением того, что приложение не отображается какnot responding
в диспетчере задач в этом случае. Он по-прежнему будет так же не реагировать на пользователя.
Способ обойти эту проблему - вызвать цикл, как уже указывали другие люди. Однако это связано с другой проблемой. Поскольку приложение попытается выполнить код VBA как можно быстрее, DoEvents вызывается с максимально достижимой скоростью, по существу полностью загружая ЦП в этом единственном потоке, что приводит к высокому, ненужному использованию ЦП и энергии и потенциально замедляет другие более важные задачи в пользовательский интерфейс.
Вот почему лучший способ заставить VBA приостановить выполнение — это комбинация обоих методов, используя, чтобы оставаться отзывчивым и избегать максимальной загрузки ЦП. Реализация этого представлена ниже.
Универсальное решение
Следующий код реализуетWaitSeconds
Sub
это приостановит выполнение на заданное количество секунд, избегая при этом всех вышеупомянутых проблем. Его можно использовать следующим образом:
Sub UsageExample()
WaitSeconds 3.5
End Sub
Это приостановит выполнение макроса на 3,5 секунды без зависания приложения и чрезмерной загрузки ЦП. Чтобы это работало, просто скопируйте следующий код в начало любого стандартного модуля кода.
#If Mac Then
#If VBA7 Then
Private Declare PtrSafe Sub USleep Lib "/usr/lib/libc.dylib" Alias "usleep" (ByVal dwMicroseconds As Long)
#Else
Private Declare Sub USleep Lib "/usr/lib/libc.dylib" Alias "usleep" (ByVal dwMicroseconds As Long)
#End If
#Else 'Windows
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If
#End If
#If Mac Then
'Sub providing a Sleep API consistent with Windows on Mac (argument in ms)
'Authors: Cristian Buse, https://stackoverflow.com/a/71176040/12287457
' Guido Witt-Dörring, https://stackoverflow.com/a/74262120/12287457
Public Sub Sleep(ByVal dwMilliseconds As Long)
Do While dwMilliseconds And &H80000000
USleep &HFFFFFED8
If dwMilliseconds < (&H418937 Or &H80000000) Then
dwMilliseconds = &H7FBE76C9 + (dwMilliseconds - &H80000000)
Else
dwMilliseconds = dwMilliseconds - &H418937
End If
Loop
Do While dwMilliseconds > &H418937
USleep &HFFFFFED8: dwMilliseconds = dwMilliseconds - &H418937
Loop
If dwMilliseconds > &H20C49B Then
USleep (dwMilliseconds * 500& Or &H80000000) * 2&
Else
USleep dwMilliseconds * 1000&
End If
End Sub
#End If
'Sub pausing code execution without freezing the app or causing high CPU usage
'Author: Guido Witt-Dörring, https://stackoverflow.com/a/74387976/12287457
Public Sub WaitSeconds(ByVal seconds As Single)
Dim currTime As Single, endTime As Single, cacheTime As Single
currTime = Timer(): endTime = currTime + seconds: cacheTime = currTime
Do While currTime < endTime
Sleep 15: DoEvents: currTime = Timer()
'The following is necessary because the Timer() function resets at 00:00
If currTime < cacheTime Then endTime = endTime - 86400! 'seconds per day
cacheTime = currTime
Loop
End Sub
[1] Дополнительную информацию о кроссплатформенности, включенной в приведенный выше код, можно найти здесь и здесь .
Если зависание приложения не является проблемой, например, при очень коротких задержках или нежелательном взаимодействии с пользователем, лучшим решением будет прямой вызов. Вот почему в приведенном выше решении он также объявлен как . Обратите внимание, что аргумент принимает значение в миллисекундах.
'Does freeze application
Sub UsageExample()
Sleep 3.5 * 1000
End Sub
Важные заметки:
Если вы хотите использовать этот код внутри модуля класса и не хотите объявлять подпрограмму в дополнительном стандартном модуле, вы можете использовать приведенный выше код, но вам нужно изменить объявление для всех
Sleep
подпадает подPrivate
потому что невозможно импортировать функции API какPublic
внутри модуля класса.Точность времени
Timer()
Функция, используемая в этом решении, лучше подходит для Windows, однако утверждение в документации о том, что разрешение на Mac составляет одну секунду, неверно. Даже на Mac разрешение лучше 0,1 секунды. Тем не менее, вы не должны ожидать разрешения намного лучше, чем ~0.1
секунды от этого решения!WaitSeconds 1
будет ждать вокруг1.015 ± 0.02
секунд в Windows.Если вы планируете использовать это, чтобы приостановить свой код на длительные периоды времени или даже в случае, с которым имеет дело OP, вы, скорее всего, используете неправильный инструмент для работы. Если вы используете Excel, рассмотрите возможность изучения. (см. следующий раздел)
Альтернативы приостановке выполнения VBA и лучшее решение для OP
Вопрос, который задал оп, не приводит к лучшему решению его проблемы. Это XY-проблема .
На самом деле нет необходимости, чтобы код VBA работал без остановок в фоновом режиме, чтобы пересчитывать рабочую книгу каждую секунду. Это типичный пример задачи, которая также может быть решена с помощью .
Подробное руководство, включая решение для копирования и вставки, для пересчета любыхRange
в любой желаемый интервал времени≥ 1s
доступен здесь .
Большое преимущество использованияApplication.OnTime
поскольку он избегает непрерывно работающего макроса и, следовательно, позволяет использовать другие макросы или другие функции, недоступные во время выполнения макросов.
Мета-анализ всех других решений в этой теме
Причина, по которой я даже написал этот ответ, заключается в том, что все другие решения, представленные в этой теме (на момент написания этого сообщения), имеют по крайней мере один из следующих двух серьезных недостатков :
- Они полностью замораживают вызывающее приложение, из-за чего оно больше не отвечает на действия пользователя, или
- Они вызывают чрезмерное использование ЦП (100% в потоке вызывающего приложения), вызывая
DoEvents
в петле.
Кроме того, многие из предлагаемых решений имеют другие проблемы:
Некоторые работают только в Windows
Некоторые работают только в Excel
Некоторые из них имеют внутреннюю погрешность более одной секунды.
У некоторых есть другие проблемы или даже ошибки
В следующей таблице представлен краткий обзор всех решений в этой теме и их особенностей.
Легенда
Объявление для Sleep в kernel32.dll не будет работать в 64-битном Excel. Это было бы немного более общим:
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If
Просто очищенная версия кода clemo - работает в Access, который не имеет функции Application.Wait.
Public Sub Pause(sngSecs As Single)
Dim sngEnd As Single
sngEnd = Timer + sngSecs
While Timer < sngEnd
DoEvents
Wend
End Sub
Public Sub TestPause()
Pause 1
MsgBox "done"
End Sub
В большинстве представленных решений используется Application.Wait, который не учитывает время (миллисекунды), уже прошедшее с момента начала подсчета секунд, поэтому внутренняя неточность составляет до 1 секунды.
Подход с таймером является наилучшим решением, но вы должны принять во внимание сброс в полночь, поэтому вот очень точный метод Sleep с использованием Timer:
'You can use integer (1 for 1 second) or single (1.5 for 1 and a half second)
Public Sub Sleep(vSeconds As Variant)
Dim t0 As Single, t1 As Single
t0 = Timer
Do
t1 = Timer
If t1 < t0 Then t1 = t1 + 86400 'Timer overflows at midnight
DoEvents 'optional, to avoid excel freeze while sleeping
Loop Until t1 - t0 >= vSeconds
End Sub
ИСПОЛЬЗУЙТЕ ЭТО ДЛЯ ТЕСТИРОВАНИЯ ЛЮБОЙ ФУНКЦИИ СНА: (откройте окно отладки: CTRL+G)
Sub testSleep()
t0 = Timer
Debug.Print "Time before sleep:"; t0 'Timer format is in seconds since midnight
Sleep (1.5)
Debug.Print "Time after sleep:"; Timer
Debug.Print "Slept for:"; Timer - t0; "seconds"
End Sub
Функции ожидания и сна блокируют Excel, и вы не можете ничего делать, пока не закончится задержка. С другой стороны, задержка цикла не дает вам точного времени ожидания.
Итак, я сделал это обходное решение, объединив немного обеих концепций. Он повторяется до тех пор, пока время не станет нужным вам.
Private Sub Waste10Sec()
target = (Now + TimeValue("0:00:10"))
Do
DoEvents 'keeps excel running other stuff
Loop Until Now >= target
End Sub
Вам просто нужно позвонить в Waste10Sec, где вам нужна задержка
Вот альтернатива для сна:
Sub TDelay(delay As Long)
Dim n As Long
For n = 1 To delay
DoEvents
Next n
End Sub
В следующем коде я заставляю мигать эффект "свечения" на кнопке прокрутки, чтобы направить пользователей к ней, если у них "возникли проблемы", использование "sleep 1000" в цикле не привело к видимому миганию, но цикл работает отлично.
Sub SpinFocus()
Dim i As Long
For i = 1 To 3 '3 blinks
Worksheets(2).Shapes("SpinGlow").ZOrder (msoBringToFront)
TDelay (10000) 'this makes the glow stay lit longer than not, looks nice.
Worksheets(2).Shapes("SpinGlow").ZOrder (msoSendBackward)
TDelay (100)
Next i
End Sub
Function Delay(ByVal T As Integer)
'Function can be used to introduce a delay of up to 99 seconds
'Call Function ex: Delay 2 {introduces a 2 second delay before execution of code resumes}
strT = Mid((100 + T), 2, 2)
strSecsDelay = "00:00:" & strT
Application.Wait (Now + TimeValue(strSecsDelay))
End Function
Я обычно использую функцию таймера, чтобы приостановить приложение. Вставьте этот код в свой
T0 = Timer
Do
Delay = Timer - T0
Loop Until Delay >= 1 'Change this value to pause time for a certain amount of seconds
Вы можете использовать Application.wait now + timevalue("00:00:01") или Application.wait now + timeserial(0,0,1)
Я сделал это, чтобы ответить на проблему:
Sub goTIMER(NumOfSeconds As Long) 'in (seconds) as: call gotimer (1) 'seconds
Application.Wait now + NumOfSeconds / 86400#
'Application.Wait (Now + TimeValue("0:00:05")) 'other
Application.EnableEvents = True 'EVENTS
End Sub