環境
- Excel for Microsoft365(64bit)
経緯
ExcelVBAで以下のような機能を要求されました。
- ボタン1を押すと監視処理Aを開始する
- 監視処理Aは50ms周期で状態の監視と、その結果をVBAフォーム上に表示する
- ボタン2を押すと処理Bを開始する
- 処理Bは1秒ごとに内部のパラメータを変更し、これを10回繰り返す
当初は監視処理Aのみ要求されていたので、ボタンイベントの中で無限ループを行う形で処理を実現していました。
その後、処理Bを追加することになりましたが、同様にボタンイベントで実装したところ、処理Bが実行されている間、監視処理Aが動かなくなりました。
これはExcelVBAがシングルスレッドで動作するためであり、二つの処理を同時に実行できないことが原因でした。
この問題を解決するため、次にあげる方法で疑似的なマルチスレッド処理を実現しました。
Application.OnTimeによる疑似マルチスレッド
Application.OnTimeは指定した時刻にプロシージャ(マクロ)を実行します。
指定した時刻、今回であれば処理Bを1秒ごとに呼び出すことで、監視処理Aが実行できるようにします。
UserForm
Private LoopActive As Boolean
Private CounterB As Integer
Private ReservedTimeB As Date
Private Sub 監視処理A_Click()
If 監視処理A.Value = True Then
'開始
LoopActive = True
Application.OnTime EarliestTime:=Now, Procedure:="FunctionA"
Else
'停止
LoopActive = False
End If
End Sub
Private Sub 処理B_Click()
If 処理B.Value = True Then
'開始
CounterB = 0
'処理Bの実行時間を保持(停止時に必要なため)
ReservedTimeB = Now + TimeValue("00:00:01")
Application.OnTime EarliestTime:=ReservedTimeB, Procedure:="FunctionB"
Else
If CounterB < 10 Then
'停止(次回予約されている時のみ
Application.OnTime EarliestTime:=ReservedTimeB, Procedure:="FunctionB", Schedule:=False
End If
End If
End Sub
Public Sub FunctionA_Loop()
While (LoopActive)
'==========
'監視処理A
'==========
'その他のイベント、タイマー処理に実行権を移す
DoEvents
Wend
End Sub
Public Sub FunctionB_Loop()
'==========
'処理B
'==========
CounterB = CounterB + 1
'次回起動予約(1秒後)
If CounterB < 10 Then
Application.OnTime EarliestTime:=ReservedTimeB, Procedure:="FunctionB"
End If
End Sub
標準モジュール
'OnTimeで呼び出される処理を定義
Public Sub FunctionA()
UserForm1.FunctionA_Loop
End Sub
Public Sub FunctionB()
UserForm1.FunctionB_Loop
End Sub
調べてみると、VBScriptやExcelを別プロセスで起動する方法もあるようですが、デバッグができなかったり、制御が難しかったりします。(バックグラウンドにExcelのプロセスが残ったり)
そういった点からApplication.OnTimeを使う方法がベストかと思います。