Как сделать паузу на определенное количество времени? (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

Важные заметки:

  1. Если вы хотите использовать этот код внутри модуля класса и не хотите объявлять подпрограмму в дополнительном стандартном модуле, вы можете использовать приведенный выше код, но вам нужно изменить объявление для всехSleepподпадает подPrivateпотому что невозможно импортировать функции API какPublicвнутри модуля класса.

  2. Точность времениTimer()Функция, используемая в этом решении, лучше подходит для Windows, однако утверждение в документации о том, что разрешение на Mac составляет одну секунду, неверно. Даже на Mac разрешение лучше 0,1 секунды. Тем не менее, вы не должны ожидать разрешения намного лучше, чем ~0.1секунды от этого решения!WaitSeconds 1будет ждать вокруг1.015 ± 0.02секунд в Windows.

  3. Если вы планируете использовать это, чтобы приостановить свой код на длительные периоды времени или даже в случае, с которым имеет дело OP, вы, скорее всего, используете неправильный инструмент для работы. Если вы используете Excel, рассмотрите возможность изучения. (см. следующий раздел)


Альтернативы приостановке выполнения VBA и лучшее решение для OP

Вопрос, который задал оп, не приводит к лучшему решению его проблемы. Это XY-проблема .

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

Подробное руководство, включая решение для копирования и вставки, для пересчета любыхRangeв любой желаемый интервал времени≥ 1sдоступен здесь .

Большое преимущество использованияApplication.OnTimeпоскольку он избегает непрерывно работающего макроса и, следовательно, позволяет использовать другие макросы или другие функции, недоступные во время выполнения макросов.


Мета-анализ всех других решений в этой теме

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

  1. Они полностью замораживают вызывающее приложение, из-за чего оно больше не отвечает на действия пользователя, или
  2. Они вызывают чрезмерное использование ЦП (100% в потоке вызывающего приложения), вызываяDoEventsв петле.

Кроме того, многие из предлагаемых решений имеют другие проблемы:

  1. Некоторые работают только в Windows

  2. Некоторые работают только в Excel

  3. Некоторые из них имеют внутреннюю погрешность более одной секунды.

  4. У некоторых есть другие проблемы или даже ошибки

В следующей таблице представлен краткий обзор всех решений в этой теме и их особенностей.

Легенда

Объявление для 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

Application.Wait Second(Now) + 1

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

Попробуй это:

Threading.thread.sleep(1000)
Другие вопросы по тегам