【Visual Basic 6.0】Sleep関数の不具合
Sleep関数の不具合と対応方法(サンプルコード付き)
開発環境
カテゴリー | 名称 | 詳細バージョン |
---|---|---|
OS | Windows XP | (SP3) Version:2002 |
プログラミング言語 | Visual Basic 6.0 | (SP6) Version:9782 |
不具合内容
現象
Sleep関数を使用しているロジックが実行され続けていると、動作が遅くなる。タスクマネージャーで確認したところ、ハンドルとメモリ使用率ともに高くなっている状況で、ハングアップしていた。
原因
Sleep関数を使用している間に発生したイベントをハンドル出来なかったため、ハングアップした状態となった。
これは、Sleep関数の不具合?で、MicrosoftもSleep関数を使用することを推奨していないらしい。
対応方法
Sleep関数から、SetWaitableTimer関数を使用した待機関数へ変更
Sleep関数について
プログラムの実行を一時停止するために、Sleep関数が多く使用されています。
しかし、Sleepにはアプリケーションが実行されているスレッドを一時停止するため、開いているウィンドウが正しく再描画されないなどの欠点があります。
対応策としてSleepの代わりに、SetWaitableTimerを使用することができます。これにより、画面の再描画やDDEメッセージの受信などが可能になります。
サンプルコード
[プロジェクト]メニューから、[モジュールの追加]をクリックして、プロジェクトに新しいモジュール(Wait.bas)を追加し、使用してください。
Wait関数は処理を止める時間を秒単位で指定することができます。
Option Explicit
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Const WAIT_ABANDONED& = &H80&
Private Const WAIT_ABANDONED_0& = &H80&
Private Const WAIT_FAILED& = -1&
Private Const WAIT_IO_COMPLETION& = &HC0&
Private Const WAIT_OBJECT_0& = 0
Private Const WAIT_OBJECT_1& = 1
Private Const WAIT_TIMEOUT& = &H102&
Private Const INFINITE = &HFFFF
Private Const ERROR_ALREADY_EXISTS = 183&
Private Const QS_HOTKEY& = &H80
Private Const QS_KEY& = &H1
Private Const QS_MOUSEBUTTON& = &H4
Private Const QS_MOUSEMOVE& = &H2
Private Const QS_PAINT& = &H20
Private Const QS_POSTMESSAGE& = &H8
Private Const QS_SENDMESSAGE& = &H40
Private Const QS_TIMER& = &H10
Private Const QS_MOUSE& = (QS_MOUSEMOVE _
Or QS_MOUSEBUTTON)
Private Const QS_INPUT& = (QS_MOUSE _
Or QS_KEY)
Private Const QS_ALLEVENTS& = (QS_INPUT _
Or QS_POSTMESSAGE _
Or QS_TIMER _
Or QS_PAINT _
Or QS_HOTKEY)
Private Const QS_ALLINPUT& = (QS_SENDMESSAGE _
Or QS_PAINT _
Or QS_TIMER _
Or QS_POSTMESSAGE _
Or QS_MOUSEBUTTON _
Or QS_MOUSEMOVE _
Or QS_HOTKEY _
Or QS_KEY)
Private Declare Function CreateWaitableTimer Lib "kernel32" _
Alias "CreateWaitableTimerA" ( _
ByVal lpSemaphoreAttributes As Long, _
ByVal bManualReset As Long, _
ByVal lpName As String) As Long
Private Declare Function OpenWaitableTimer Lib "kernel32" _
Alias "OpenWaitableTimerA" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal lpName As String) As Long
Private Declare Function SetWaitableTimer Lib "kernel32" ( _
ByVal hTimer As Long, _
lpDueTime As FILETIME, _
ByVal lPeriod As Long, _
ByVal pfnCompletionRoutine As Long, _
ByVal lpArgToCompletionRoutine As Long, _
ByVal fResume As Long) As Long
Private Declare Function CancelWaitableTimer Lib "kernel32" ( _
ByVal hTimer As Long)
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Private Declare Function MsgWaitForMultipleObjects Lib "user32" ( _
ByVal nCount As Long, _
pHandles As Long, _
ByVal fWaitAll As Long, _
ByVal dwMilliseconds As Long, _
ByVal dwWakeMask As Long) As Long
Public Sub Wait(lNumberOfSeconds As Long)
Dim ft As FILETIME
Dim lBusy As Long
Dim lRet As Long
Dim dblDelay As Double
Dim dblDelayLow As Double
Dim dblUnits As Double
Dim hTimer As Long
hTimer = CreateWaitableTimer(0, True, App.EXEName & "Timer")
If Err.LastDllError = ERROR_ALREADY_EXISTS Then
' タイマーがすでに存在する かつ 適切なアクセス権を持っている場合は正常
Else
ft.dwLowDateTime = -1
ft.dwHighDateTime = -1
lRet = SetWaitableTimer(hTimer, ft, 0, 0, 0, 0)
End If
' 単位をナノ秒に変換
dblUnits = CDbl(&H10000) * CDbl(&H10000)
dblDelay = CDbl(lNumberOfSeconds) * 1000 * 10000
' ※Wait関数をミリ秒で指定したい場合は、上のコードをこちらに置き換えて下さい
' dblDelay = CDbl(lNumberOfSeconds) * 10 * 1000
' SetWaitableTimerの待機時間にオフセット時間を使用
ft.dwHighDateTime = -CLng(dblDelay / dblUnits) - 1
dblDelayLow = -dblUnits * (dblDelay / dblUnits - Fix(dblDelay / dblUnits))
If dblDelayLow < CDbl(&H80000000) Then
' FILETIME構造体のオーバーフローチェック
dblDelayLow = dblUnits + dblDelayLow
End If
ft.dwLowDateTime = CLng(dblDelayLow)
lRet = SetWaitableTimer(hTimer, ft, 0, 0, 0, False)
Do
' QS_ALLINPUTを指定し、メッセージの割り込みを許可する
lBusy = MsgWaitForMultipleObjects(1, hTimer, False, INFINITE, QS_ALLINPUT&)
DoEvents
Loop Until lBusy = WAIT_OBJECT_0
' 使い終わったらハンドルを閉じる
CloseHandle hTimer
End Sub
引用
追記
各API関数の微妙な違いについて
-
Sleep
指定された時間にわたって、現在のスレッドの実行を中断する。 -
SleepEx
指定された時間にわたって、現在のスレッドの実行を中断する。
次の条件のいずれかが満たされると、実行を再開する。- I/O 完了コールバック関数が呼び出し
- 非同期プロシージャコール(APC)オブジェクトが、スレッドの ACP キューに置かれた
- タイムアウト時間が経過
-
WaitForMultipleObjects
指定したオブジェクトのいずれか1つまたはすべてがシグナル状態になったとき、またはタイムアウト時間が経過したとき制御を戻す。 -
WaitForMultipleObjectsEx
指定したオブジェクトのいずれか1つまたはすべてがシグナル状態になったとき、または次の条件のいずれかが満たされると、制御を返す。- I/O 完了コールバック関数が呼び出し
- 非同期プロシージャコール(APC)オブジェクトが、スレッドの ACP キューに置かれた
- タイムアウト時間が経過
Discussion