[VB6][VBA] Timer 控制項 - 時間解析度過低的解決方法

[VB6][VBA] Timer 控制項 - 時間解析度過低的解決方法

 

  1. 經常會用到 Timer 計時器來做一些定時執行的動作,在 Interval 訂得很低時會出現不穩定或是 Loss 了事件的情形。
  2. 當然原因有很多,其中一個原因就是 Timer 控制項用來比對時間的 Tick 過於粗糙大約是 1/64 秒才發生一次。
  3. 就是在一秒之內 Timer() 的值實際上只變化了 64 次,換句話說只要你用的 Interval < 1/64 秒(<15.625ms)時你的程式就不夠準確了。
  4. 用下面程式碼用 Do…Loop 跑一秒,只要發現 Timer 值和前一次不同就記錄下來,可以看出它只記下了 64 次:

    
    Private Sub Command1_Click()
        t0 = Timer
        t1 = t0
        Do While Timer - t0 < 1
            If Timer <> t1 Then
                n = n + 1
                t1 = Timer
                Debug.Print n, t1 - t0
            End If
        Loop
    End Sub
    
    '上面程式碼的測試結果:
    
    ' 1             0.015625
    ' 2             0.03125
    ' 3             0.046875
    ' 4             0.0625
    ' 5             0.078125
    ' 6             0.09375
    ' 7             0.109375
    ' 8             0.125
    ' 9             0.140625
    ' 10            0.15625
    ' 11            0.171875
    ' 12            0.1875
    ' 13            0.203125
    ' 14            0.21875
    ' 15            0.234375
    ' 16            0.25
    ' 17            0.265625
    ' 18            0.28125
    ' 19            0.296875
    ' 20            0.3125
    ' 21            0.328125
    ' 22            0.34375
    ' 23            0.359375
    ' 24            0.375
    ' 25            0.390625
    ' 26            0.40625
    ' 27            0.421875
    ' 28            0.4375
    ' 29            0.453125
    ' 30            0.46875
    ' 31            0.484375
    ' 32            0.5
    ' 33            0.515625
    ' 34            0.53125
    ' 35            0.546875
    ' 36            0.5625
    ' 37            0.578125
    ' 38            0.59375
    ' 39            0.609375
    ' 40            0.625
    ' 41            0.640625
    ' 42            0.65625
    ' 43            0.671875
    ' 44            0.6875
    ' 45            0.703125
    ' 46            0.71875
    ' 47            0.734375
    ' 48            0.75
    ' 49            0.765625
    ' 50            0.78125
    ' 51            0.796875
    ' 52            0.8125
    ' 53            0.828125
    ' 54            0.84375
    ' 55            0.859375
    ' 56            0.875
    ' 57            0.890625
    ' 58            0.90625
    ' 59            0.921875
    ' 60            0.9375
    ' 61            0.953125
    ' 62            0.96875
    ' 63            0.984375
    ' 64            1
    '

  5. 可用  QueryPerformanceCounter 和 QueryPerformanceFrequency 這兩支 API 來解決,它是用 CPU 的 Clock 做為比對的依據。
  6. 改過的程式碼如下:可以看出改善多了(在 0.1 秒之內用 0.002 秒做間隔,事件發生次數為(100ms / 2ms -1 = 49 次)。

    
    Private Sub Command1_Click()
        Dim 總執行秒數 As Double
        Dim 事件週期 As Double
        Dim 觸發次數 As Long
        Dim 經過時間 As Single
        Dim 迴圈執行次數 As Long
        Dim 時鐘頻率 As Currency
        Dim 階段計時起點 As Currency
        Dim Ticks As Currency
        Dim 工作中 As Boolean
        總執行秒數 = 0.1
        事件週期 = 0.002
        觸發次數 = 0
        r1 = QueryPerformanceFrequency(時鐘頻率)
        r2 = QueryPerformanceCounter(階段計時起點)
        t0 = 階段計時起點
        Debug.Print "CPU CLock ="; CDbl(時鐘頻率 * 10); "Mhz"
           Do
            迴圈執行次數 = 迴圈執行次數 + 1
            r3 = QueryPerformanceCounter(Ticks)
            經過時間 = (Ticks - 階段計時起點) / 時鐘頻率
            If 經過時間 >= (事件週期) And Not 工作中 Then
                工作中 = True
                階段計時起點 = Ticks
                觸發次數 = 觸發次數 + 1
                '-----計時器事件放在這裡--------------------
                Debug.Print ("第 " & 觸發次數 & " 次事件, N= " & 迴圈執行次數 & ", T=" & Format(CDbl(經過時間), "##.#####0 秒"))
                '-------------------------------------------
                迴圈執行次數 = 0
                工作中 = False
            End If
            DoEvents
        Loop While 階段計時起點 - t0 < 總執行秒數 * 時鐘頻率
    End Sub
    
    '上面程式碼的測試結果:
    '
    'CPU CLock = 2862.568 Mhz
    '第 1 次事件, N= 1, T=.003135 秒
    '第 2 次事件, N= 1, T=.002432 秒
    '第 3 次事件, N= 1, T=.002214 秒
    '第 4 次事件, N= 1, T=.002150 秒
    '第 5 次事件, N= 1, T=.002111 秒
    '第 6 次事件, N= 1, T=.002075 秒
    '第 7 次事件, N= 1, T=.002219 秒
    '第 8 次事件, N= 1, T=.002031 秒
    '第 9 次事件, N= 21, T=.002002 秒
    '第 10 次事件, N= 48, T=.002000 秒
    '第 11 次事件, N= 65, T=.002001 秒
    '第 12 次事件, N= 83, T=.002001 秒
    '第 13 次事件, N= 107, T=.002001 秒
    '第 14 次事件, N= 126, T=.002001 秒
    '第 15 次事件, N= 54, T=.002002 秒
    '第 16 次事件, N= 158, T=.002002 秒
    '第 17 次事件, N= 192, T=.002002 秒
    '第 18 次事件, N= 215, T=.002002 秒
    '第 19 次事件, N= 238, T=.002001 秒
    '第 20 次事件, N= 260, T=.002001 秒
    '第 21 次事件, N= 278, T=.002001 秒
    '第 22 次事件, N= 305, T=.002002 秒
    '第 23 次事件, N= 1406, T=.002000 秒
    '第 24 次事件, N= 1846, T=.002001 秒
    '第 25 次事件, N= 1866, T=.002000 秒
    '第 26 次事件, N= 1896, T=.002001 秒
    '第 27 次事件, N= 1790, T=.002000 秒
    '第 28 次事件, N= 1793, T=.002001 秒
    '第 29 次事件, N= 1788, T=.002000 秒
    '第 30 次事件, N= 1785, T=.002000 秒
    '第 31 次事件, N= 1647, T=.002001 秒
    '第 32 次事件, N= 1769, T=.002000 秒
    '第 33 次事件, N= 1795, T=.002001 秒
    '第 34 次事件, N= 1767, T=.002000 秒
    '第 35 次事件, N= 1782, T=.002001 秒
    '第 36 次事件, N= 1776, T=.002000 秒
    '第 37 次事件, N= 1781, T=.002001 秒
    '第 38 次事件, N= 1677, T=.002000 秒
    '第 39 次事件, N= 1607, T=.002000 秒
    '第 40 次事件, N= 1779, T=.002000 秒
    '第 41 次事件, N= 1761, T=.002001 秒
    '第 42 次事件, N= 1691, T=.002001 秒
    '第 43 次事件, N= 1765, T=.002001 秒
    '第 44 次事件, N= 1717, T=.002001 秒
    '第 45 次事件, N= 1738, T=.002000 秒
    '第 46 次事件, N= 1787, T=.002000 秒
    '第 47 次事件, N= 1895, T=.002001 秒
    '第 48 次事件, N= 1997, T=.002000 秒
    '第 49 次事件, N= 1996, T=.002001 秒
    '
    '

  7. 至於在 VB.net 上的做法可用 Stopwatch 類別的「Frequency」和「ElapsedTicks」來代替 QueryPerformanceFrequency 和 QueryPerformanceCounter,原理相同,程式碼下回再貼了。

ku3