delay a batch file in under a second?

后端 未结 6 1897
渐次进展
渐次进展 2020-12-01 18:59

Is there any way to delay a batch file in under a second, for example 10 milliseconds?

I have tried to write this:

ping localhost -n 1 -w 10
<         


        
6条回答
  •  伪装坚强ぢ
    2020-12-01 19:44

    Below I have three different pure script solutions for providing sub-second delays within a batch script. All of these solutions have a minimum delay that varies from machine to machine. As long as the minimum delay is exceeded, they each are generally accurate to within 10 milliseconds.

    I used Aacini's test harness to test the accuracy of all three methods so the results can be compared directly with his results.


    Batch Macro

    This pure batch solution is very simple in concept. I get the current time (with 1/100 second precision), add the delay, and wait for that new time within a tight loop.

    I've packaged this logic in an advanced batch macro technique that is tricky to understand and develop, but easy to use. By using a macro, there is no delay introduced by CALL. See Current batch macro syntax and historical development of batch macros if you want to learn more about batch macro theory, and how to write your own.

    This pure batch solution has the smallest minimum delay of the three. On the two machines I've tested, the minimum delay ranged from ~15 to ~30 msec. This macro only supports delays less than 24 hours.

    The major drawback with this macro is it consumes CPU resources - pegging a single CPU machine with a single core to 100% usage throughout the delay.

    @echo off
    setlocal disableDelayedExpansion
    
    ::********  Define the @delay macro  ****************
    
    :: define LF as a Line Feed (newline) character
    set ^"LF=^
    
    ^" Above empty line is required - do not remove
    
    :: define a newline with line continuation
    set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
    
    set @delay=for %%# in (1 2) do if %%#==2 (%\n%
      for /f "tokens=1-4 delims=:.," %%a in ("!time: =0!") do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100, arg1/=10"%\n%
      cmd /v:on /c for /l %%. in (^^^) do @for /f "tokens=1-4 delims=:.," %%a in ("^!time: =0^!"^^^) do @set /a "t2=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100,tDiff=t2-t1"^^^>nul^^^&(if ^^^^!tDiff^^^^! lss 0 set /a tDiff+=8640000^^^>nul^^^)^^^&if ^^^^!tDiff^^^^! geq ^^^^!arg1^^^^! exit%\n%
      endlocal%\n%
    ) else setlocal enableDelayedExpansion^&set arg1=
    
    
    ::**********  Demonstrate usage  ********************
    echo Delaying for 1.25 seconds ...
    %@delay% 1250
    echo done.
    echo(
    
    
    ::***********  Testing accuracy  ********************
    setlocal enableDelayedExpansion
    echo Testing accuracy:
    set runs=10
    for %%t in (10 20 30 40 50 70 100 250 500 1000) do (
    
      (
      set t0=!time!
      for /l %%p in (1,1,%runs%) do %@delay% %%t
      set t1=!time!
      )
    
      for /f "tokens=1-8 delims=:.," %%a in ("!t0: =0!:!t1: =0!") do (
        set /a "a=(((1%%e-1%%a)*60)+1%%f-1%%b)*6000+1%%g%%h-1%%c%%d, a+=(a>>31) & 8640000"
      )
    
      set /a average_time=a*10/runs
      echo(Input:%%t ms - Output: Average time !average_time! ms
    )
    

    -- Sample results --

    Delaying for 1.25 seconds ...
    done.
    
    Testing accuracy:
    Input:10 ms - Output: Average time 14 ms
    Input:20 ms - Output: Average time 20 ms
    Input:30 ms - Output: Average time 30 ms
    Input:40 ms - Output: Average time 40 ms
    Input:50 ms - Output: Average time 50 ms
    Input:70 ms - Output: Average time 70 ms
    Input:100 ms - Output: Average time 100 ms
    Input:250 ms - Output: Average time 250 ms
    Input:500 ms - Output: Average time 500 ms
    Input:1000 ms - Output: Average time 1000 ms
    


    SLEEP.BAT - Hybrid JScript/Batch (Locale Agnostic)

    This is my favorite solution. I CALL my SLEEP.BAT utility, passing in the desired delay, plus the current %time% at the time of the CALL. The batch script calls JScript embedded within the same file, which subtracts the CALL time from the current time to determine how much time has already elapsed, and then subtracts this value from the delay time to figure out how long the script should sleep. This solution uses the WScript.sleep() method, which is event driven and does not consume CPU resources.

    The batch script will supply its own %time% value if it is not passed in, but then the accuracy may suffer a bit due to the length of time it takes to CALL the utility.

    It takes a significant time for CSCRIPT to initialize, so the minimum delay is longer than the macro solution. On my two machines, the minimum delay ranged from ~30 to ~55 msec.

    SLEEP.BAT should work on any Windows machine, regardless how your locale displays date and time. The only problem I have with this utility is it can give the wrong delay if called the instant before changeover from standard to daylight savings time, or vice-versa.

    SLEEP.BAT

    @if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment
    @goto :batch
    :::
    :::SLEEP.BAT  msec  [%time%]
    :::SLEEP.BAT  /?
    :::
    :::  Suspend processing for msec milliseconds. The optional %time% argument
    :::  can be added to improve timing accuracy. If called within a FOR loop,
    :::  then !time! should be used instead, after enabling delayed expansion.
    :::
    :::  There is a startup time for SLEEP.BAT that limits the shortest delay
    :::  possible. The startup time varies from machine to machine. Delays longer
    :::  than the minimum are usually accurate to within 10 msec if the %time%
    :::  argument is provided. One exception is when SLEEP.BAT is called the
    :::  instant before changing from standard to daylight savings time, in which
    :::  case the delay is extended by the startup time. The other exception occurs
    :::  when changing from daylight savings to standard, in which case the delay
    :::  never exceeds the startup time.
    :::
    :::  A single /? argument displays this help.
    :::
    :::  SLEEP.BAT Version 1.0 - written by Dave Benham
    :::
    
    ============= :Batch portion ===========
    @echo off
    if "%~1" equ "/?" (
      for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
      exit /b 0
    ) else cscript //E:JScript //nologo "%~f0" %* %time%
    exit /b
    
    ============ JScript portion ==========*/
    try {
      var arg2 = WScript.Arguments.Item(1).split(/[:.,]/);
      var start = new Date();
      if (start.getHours()0) WScript.sleep( (adjustedDelay>delay) ? delay : adjustedDelay );
      WScript.Quit(0);
    } catch(e) {
      WScript.Stderr.WriteLine("SLEEP.BAT - Invalid call");
      WScript.Quit(1);
    }
    

    Test harness and demonstration of usage

    @echo off
    setlocal enableDelayedExpansion
    set runs=10
    for %%t in (20 30 40 50 70 100 250 500 1000) do (
    
      (
      set t0=!time!
      for /l %%p in (1,1,%runs%) do call sleep %%t !time!
      set t1=!time!
      )
    
      for /f "tokens=1-8 delims=:.," %%a in ("!t0: =0!:!t1: =0!") do (
        set /a "a=(((1%%e-1%%a)*60)+1%%f-1%%b)*6000+1%%g%%h-1%%c%%d, a+=(a>>31) & 8640000"
      )
    
      set /a average_time=a*10/runs
      echo(Input:%%t ms - Output: Average time !average_time! ms
    )
    

    -- Sample Output --

    Input:20 ms - Output: Average time 56 ms
    Input:30 ms - Output: Average time 55 ms
    Input:40 ms - Output: Average time 54 ms
    Input:50 ms - Output: Average time 56 ms
    Input:70 ms - Output: Average time 71 ms
    Input:100 ms - Output: Average time 100 ms
    Input:250 ms - Output: Average time 253 ms
    Input:500 ms - Output: Average time 501 ms
    Input:1000 ms - Output: Average time 1001 ms
    


    LocaleSleep.bat - Hybrid JScript/Batch (Locale Dependent)

    This is nearly identical to SLEEP.BAT, except it passes in "%date% %time%" instead of "%time%. The advantage is it no longer has problems with the changeover between standard and daylight savings time. The major disadvantage is it is locale dependent. It only works if %date% is parsed properly by the Date object parse() method. I know this should work on most U.S. machines, plus some others. Variants could be written to support other locales, but each would be locale dependent.

    LocaleSleep.bat

    @if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment
    @goto :batch
    :::
    :::LocaleSleep.bat  msec  ["%date% %time%"]
    :::LocaleSleep.bat  /?
    :::
    :::  Suspend processing for msec milliseconds. The optional "%date% %time%"
    :::  argument can be added to improve timing accuracy. If called within a
    :::  FOR loop, then "!date! !time!" should be used instead, after enabling
    :::  delayed expansion.
    :::
    :::  This utility is locale specific. It only works properly if %date% is in
    :::  a format that is parsed properly by the Date object parse() method.
    :::
    :::  There is a startup time for SLEEP.BAT that limits the shortest delay
    :::  possible. The startup time varies from machine to machine. Delays longer
    :::  than the minimum are usually accurate to within 10 msec if the
    :::  "%date% %time%" argument is provided.
    :::
    :::  A single /? argument displays this help.
    :::
    :::  LocaleSleep.bat Version 1.0 - written by Dave Benham
    :::
    
    ============= :Batch portion ===========
    @echo off
    if "%~1" equ "/?" (
      for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
      exit /b 0
    ) else cscript //E:JScript //nologo "%~f0" %* "%date% %time%"
    exit /b
    
    ============ JScript portion ==========*/
    try {
      var arg2 = WScript.Arguments.Item(1);
      var delay = Number(WScript.Arguments.Item(0)) - ((new Date())-(new Date(arg2.slice(0,-3))).setMilliseconds( Number(arg2.slice(-2))*10 ));
      if (delay>0) WScript.sleep(delay);
      WScript.Quit(0);
    } catch(e) {
      WScript.Stderr.WriteLine("localeSleep.bat - Invalid call");
      WScript.Quit(1);
    }
    

    Test harness and demonstration of usage

    @echo off
    setlocal enableDelayedExpansion
    set runs=10
    for %%t in (20 30 40 50 70 100 250 500 1000) do (
    
      (
      set t0=!time!
      for /l %%p in (1,1,%runs%) do call localeSleep %%t "!date! !time!"
      set t1=!time!
      )
    
      for /f "tokens=1-8 delims=:.," %%a in ("!t0: =0!:!t1: =0!") do (
        set /a "a=(((1%%e-1%%a)*60)+1%%f-1%%b)*6000+1%%g%%h-1%%c%%d, a+=(a>>31) & 8640000"
      )
    
      set /a average_time=a*10/runs
      echo(Input:%%t ms - Output: Average time !average_time! ms
    )
    

    Results are virtually identical to SLEEP.BAT

提交回复
热议问题