@riverWave

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【VB.NET】System.Windows.Forms.Timerを長時間動作させると実行時刻が徐々にずれる

実現したいこと

  • 常駐アプリで、毎分ほぼ00秒に処理を実行したい
  • 長時間動作させても、実行開始時刻が徐々に遅れないようにしたい

前提

VB.NETの.NET Framework 4.8でWindows Formsアプリケーションを作成しています。

このアプリは常駐アプリとしてWindows 11上で長時間動作させています。

毎分1回DBへの登録処理を行っており、処理開始時刻を毎分ほぼ00秒に合わせるため
TimerのTick処理内で以下のような処理を行っています。

TimerをStop()する
DB登録処理を実行する
DateTime.Nowから次の00秒までの待機時間を計算する
Timerを再生成する
計算した待機時間をIntervalに設定してStart()する

この方法であれば、DB登録処理にかかった時間が次回以降に累積することはないと考えていました。
実際、アプリ起動直後はほぼ00秒に処理が開始されています。
しかし、長時間動作させると正分からの遅延が徐々に大きくなります。
※Windows 10で同じアプリを動作させていた際には、このような現象は発生していませんでした。

発生している問題・エラーメッセージ

エラーメッセージはありません。
処理開始時刻が時間の経過とともに徐々に遅れて行きます。

アプリ起動直後:

2026/09/09 00:00:00 履歴テーブル書き込み開始 : 2026/09/09 00:00:00
2026/09/09 00:00:00 DB登録待ち待機開始 : 2026/09/09 00:00:00
2026/09/09 00:00:06 DB登録待ち待機終了 : 2026/09/09 00:00:06
2026/09/09 00:00:08 書き込みOK : 2026/09/09 00:00:08
2026/09/09 00:00:08 起動時間の調整 [ 51.98 ]秒 待機
2026/09/09 00:01:00 履歴テーブル書き込み開始 : 2026/09/09 00:01:00
2026/09/09 00:01:00 DB登録待ち待機開始 : 2026/09/09 00:01:00
2026/09/09 00:01:06 DB登録待ち待機終了 : 2026/09/09 00:01:06
2026/09/09 00:01:08 書き込みOK : 2026/09/09 00:01:08
2026/09/09 00:01:09 起動時間の調整 [ 50.949 ]秒 待機

長時間動作時(1行目、6行目の開始時間が02になっています。):

2026/09/15 00:00:02 履歴テーブル書き込み開始 : 2026/09/15 00:00:02
2026/09/15 00:00:04 DB登録待ち待機開始 : 2026/09/15 00:00:04
2026/09/15 00:00:12 DB登録待ち待機終了 : 2026/09/15 00:00:12
2026/09/15 00:00:17 書き込みOK : 2026/09/15 00:00:17
2026/09/15 00:00:19 起動時間の調整 [ 40.110 ]秒 待機
2026/09/15 00:01:02 履歴テーブル書き込み開始 : 2026/09/15 00:01:02
2026/09/15 00:01:05 DB登録待ち待機開始 : 2026/09/15 00:01:05
2026/09/15 00:01:13 DB登録待ち待機終了 : 2026/09/15 00:01:13
2026/09/15 00:01:17 書き込みOK : 2026/09/15 00:01:17
2026/09/15 00:01:19 起動時間の調整 [ 40.380 ]秒 待機

該当のソースコード

 Private Sub oTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles oTimer.Tick

     Dim bRet As Boolean

     '' タイマー停止
     oTimer.Stop()

     Try
         '' ログ名設定
         If Mid(m_LogName, 9, 8) <> Format(Now, "yyyyMMdd") Then
             m_LogName = "OPE_PRO_" & Format(Now, "yyyyMMdd") & ".log"
         End If

         '' 履歴テーブルへ書き込み
         'PrvS_AddHist("_/_/_/_/_/_/_/_/_/_/書き込み開始_/_/_/_/_/_/_/_/_/_/")
         PrvS_AddHist("履歴テーブル書き込み開始 : " & CStr(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")))

         bRet = PrvF_WriteOpeHist()

         If bRet = False Then
             PrvS_AddHist("書き込みNG : " & CStr(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")))
             Return
         End If

         PrvS_AddHist("書き込みOK : " & CStr(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")))

     Catch ex As Exception
         PrvS_AddHist("システムエラー" & ex.Message)
     End Try

     '' 正分に動作するように待つ
     Dim dt As DateTime = DateTime.Now
     Dim nSec As Integer = CInt(dt.Second)
     Dim nMilSec As Integer = CInt(dt.Millisecond)
     Dim nInterval As Integer = (1000 * (59 - nSec)) + (1000 - nMilSec)

     '' タイマオブジェクトを生成する
     oTimer = New Timer()
     oTimer.Interval = (nInterval)

     PrvS_AddHist("起動時間の調整 [ " & nInterval & " ]ms 待機")

     oTimer.Start()

 End Sub

試したこと

  • 別の機器にて同様の環境を用意し実施
      →同様の現象を確認しました。

補足情報(FW/ツールのバージョンなど)

System.Windows.Forms.Timerの仕様、UIスレッドの処理、Windows 10とWindows 11の違いなど
どのような原因が考えられるかを知りたいです。

OS:Windows 11
開発環境:Visual Studio 2022
言語:VB.NET
.NET Framework:4.8
アプリケーション:Windows フォーム アプリケーション
常駐アプリとして長時間動作
Timer:System.Windows.Forms.Timer
DBへの登録処理あり
※同じアプリをWindows 10で動作させていた際には今回のような現象は発生していませんでした。

0 likes

6Answer

起動直後と長時間実行後のログを比較すると、履歴テーブル書き込みの処理時間が延びていることが分かります。

2026/09/09 00:01:00 履歴テーブル書き込み開始 : 2026/09/09 00:01:00
2026/09/09 00:01:00 DB登録待ち待機開始 : 2026/09/09 00:01:00

2026/09/15 00:01:02 履歴テーブル書き込み開始 : 2026/09/15 00:01:02
2026/09/15 00:01:05 DB登録待ち待機開始 : 2026/09/15 00:01:05

もしログ出力 PrvS_AddHist("起動時間の調整 [ " & nInterval & " ]ms 待機") にも時間がかかるのであれば、 nInterval を計算してから oTimer.Start() する間に誤差が生じることになります。ログ出力より前に oTimer.Start() すべきです。

1Like

Comments

  1. @riverWave

    Questioner

    ご回答ありがとうございます。
    仰る通りログ出力分の誤差が出てしまいますね。見落としておりました。
    もうお一方からいただいたアドバイスも含め処理を見直しましたところ、遅延が全くでなくなりました。
    大変ありがとうございました。:relaxed:

公式のドキュメントをよく見て下さい。

Windows フォーム タイマー コンポーネントはシングル スレッドであり、55 ミリ秒の精度に制限されています。 より精度の高いマルチスレッド タイマーが必要な場合は、Timer名前空間の System.Timers クラスを使用します。

System.Windows.Forms.TimerはUI向けのタイマーです。古い仕様に引っ張られているので多少の遅延を許容出来る物にしか使えません。精度を求めるのならSystem.Timers.Timerを使うようにとあります。


タイマーに関してまとめられている方の記事がありますね。

やはり、Timers.Timer以外は独特の癖のような物がありそうです。

1Like

Comments

  1. @riverWave

    Questioner

    ご回答及びタイマーに関する記事までご丁寧に共有していただきありがとうございます。

    System.Windows.Forms.TimerをSystem.Timers.Timerに置き換えてから1週間ほど動作させておりますが、現在のところ遅延は発生しておりません。

    タイマー処理内には画面更新処理も含まれておりましたが
    System.Timers.Timerを使用する場合は遅延が解消され、大変勉強になりました。

    大変ありがとうございました。

解決済みのところに補足です。お二方の回答のとおり、原因は主に「UIスレッドの Forms.Timer の遅延」と「Start() までの誤差」だと思います。加えて、設計面でも再発しにくくできます。

ポイント:Now から次の00秒までを毎回計算する方式をやめ、「次回の目標時刻」を絶対時刻で持つ

現在の方式は、Tick のたびに DateTime.Now から待機時間を作るため、Tick の遅延や処理中の誤差が毎回少しずつ次回の基準に混ざります。目標時刻を target = target.AddMinutes(1) のように前回の目標時刻から進めれば、遅延が累積しません。

vb
Private Async Function RunEveryMinuteAsync(ct As Threading.CancellationToken) As Task
Dim n = DateTime.Now
Dim target = New DateTime(n.Year, n.Month, n.Day, n.Hour, n.Minute, 0).AddMinutes(1)

Do While Not ct.IsCancellationRequested
    Dim wait = target - DateTime.Now
    If wait > TimeSpan.Zero Then Await Task.Delay(wait, ct)

    ' DB登録はUIスレッドの外で実行する
    Await Task.Run(Function() PrvF_WriteOpeHist())

    target = target.AddMinutes(1)
    ' 処理が1分を超えた場合は、遅れた分をスキップする
    Do While target <= DateTime.Now
        target = target.AddMinutes(1)
    Loop
Loop

End Function
画面更新が必要な部分だけ、UIスレッドに戻して実行します(Await の後は UI スレッドに戻ります)。
ct のキャンセル時は OperationCanceledException が出るので、Try/Catch で受けてください。
上記は動作確認をしていない参考コードです。

また、Windows 10 と 11 の違いについては、私の理解では、Windows 11 では最小化・非表示・省電力状態のプロセスにタイマー分解能の優遇が与えられにくくなっているため、常駐アプリではタイマーの揺れが大きく出る可能性があります。ただしこれは断定できないので、Forms.Timer が UI スレッドの負荷で遅れることのほうが主因かと思います。

1Like

Timer以外で負荷がかからないこの方法については盲点でした。

確かに待機にUIスレッドを使用して誤差を出すよりも
非同期待機処理で絶対時刻に合わせた方が確実ですね。

補足情報大変参考になります!
ご回答ありがとうございました。

1Like

This answer has been deleted for violation of our Terms of Service.

This answer has been deleted for violation of our Terms of Service.

Your answer might help someone💌