忍者ブログ

VB.NET-TIPS などプログラミングについて

VB.NETのTIPS(小技集)を中心に、Javascript、PHP その他のプログラミングについて少し役に立つ情報を発信します。いわゆる個人的な忘備録ですが、みなさんのお役に立てれば幸いです。

現在のイベント処理が終了するまで他のイベント処理は実行されない

一般的に VB.NET において現在実行しているイベント処理が終了するまでは、他のイベント処理は実行されない様になっています。
イベント処理は実行しているプログラムから見て、外部から引き起こされる要因により発生します。 例えば、マウスのクリックイベント、キー入力のキーダウンイベント、タイマコントロールからのチックイベントなどがあります。

それぞれのイベントが発生した順番にイベント処理は順次実行されていきます。 同時にキーやマウスが動作しても、内部的には必ず順番が振られて、わずかな時間差で順次の処理になってしまいます。 多くのイベントが発生した場合はそれぞれが持ち行列的に待たされて順次処理されます。

それでは簡単な例で順次示していきます。



楽天市場



■フォームにタイマコントロールを1個設置しタイマイベント時刻表示

先ずはフォームにタイマコントロールを1個設置し、タイマイベントでデバッグ出力ウインドウへの時刻表示を行います。 ソースは以下の様になります。

Public Class frmMulEvent

    Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 「Timer1」設定・開始
        Me.Timer1.Interval = 2000   ' 2秒毎のイベント発生
        Me.Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss"))
    End Sub
End Class

実行結果が出力ウインドウには以下様に2秒毎に時刻が表示されます。

Start...
Timer1 : 10:28:23
Timer1 : 10:28:25
Timer1 : 10:28:27
Timer1 : 10:28:29


■タイマイベント内に時間の掛かる処理をさせる

タイマイベントの中に時間のかかる処理を追加して、出力ウインドウへの表示を見てみます。

Public Class frmMulEvent

    Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 「Timer1」設定・開始
        Me.Timer1.Interval = 2000   ' 2秒毎のイベント発生
        Me.Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss"))
        ' 時間の掛かる処理を[Sleep]で行う
        System.Threading.Thread.Sleep(1000 * 5) '5秒待つ処理
    End Sub
End Class

タイマコントロールのインターバルは2秒ですが、タイマイベント処理では5秒待つ処理を行っていますので、 出力ウインドウには以下様に5秒毎に時刻が表示されます。

Start...
Timer1 : 10:55:43
Timer1 : 10:55:48
Timer1 : 10:55:53
Timer1 : 10:55:58


タイマイベント処理がインターバルよりも超えているためにイベントは発生せずに、5秒毎の時刻表示になります。 この5秒間に発生しているタイマイベントはキャンセルされている様に見えます。
本当のことを言えば、この様な処理は問題があります。実際にはイベント内の処理時間はインターバルの時間内に終わる様にするべきだと思います。

但し逆に言えば、タイマイベント処理自身が処理しているときは再度自分自身が呼ばれる(このことを再入と言います)ことは無いことになります。 つまり二重に処理されることはありませんので、これを逆手に取ってもいい場合があるかもしれません。

■タイマイベント内に再入を引き起こす処理をさせてみる

タイマイベントの処理で System.Threading.Thread.Sleep メソッドで時間待ちをしていた部分をメッセージボックスの表示にしてみます。 ソースは以下の通りです。

Public Class frmMulEvent

    Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 「Timer1」設定・開始
        Me.Timer1.Interval = 2000   ' 2秒毎のイベント発生
        Me.Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss"))
        ' メッセージBOXを表示させる
        MsgBox("Timer1 のイベント処理中")
    End Sub
End Class

これを実行すると以下の様にメッセージボックスが順次表示されてしまいます。 このままにしておくと無限に繰り返されて、やがて限界が来たところでエラーで止まると思います。

この様な現象が起こるのは、メッセージボックスの処理の中でイベントを受け付ける様になっているためです。 メッセージボックス自身にボタンコントロールが在るため、このボタンのクリックイベントを受け付けるためで、その他のイベントも拾ってしまう為です。

■タイマコントロールを2個設置して1個のタイマイベント内に時間の掛かる処理をさせる

Public Class frmMulEvent

    Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' ラベルに現在時刻表示
        Debug.Print("Start...")
        ' 「Timer1」設定・開始
        Me.Timer1.Interval = 2000   ' 2秒毎のインターバル
        Me.Timer1.Start()
        ' 「Timer2」設定・開始
        Me.Timer2.Interval = 4000   ' 4秒毎のインターバル
        Me.Timer2.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss"))
        System.Threading.Thread.Sleep(1000 * 5)
    End Sub

    Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
        ' 出力ウインドウに現在時刻表示
        Debug.Print("...Timer2 : " & Now.ToString("hh:mm:ss"))
    End Sub

End Class

このソースの実行結果は以下の通りです。Timer2 のタイマイベントは時々阻止されているのがわかると思います。

Start...
Timer1 : 02:02:15
...Timer2 : 02:02:20
Timer1 : 02:02:20
...Timer2 : 02:02:25
Timer1 : 02:02:25
Timer1 : 02:02:30
...Timer2 : 02:02:35
Timer1 : 02:02:35
Timer1 : 02:02:40
...Timer2 : 02:02:45


■ボタンコントロールを2個設置して1個のクリックイベント内に時間の掛かる処理をさせる

以下の様にフォーム上にボタンコントロールを2個設置して Button1 のクリックイベント内に時間の掛かる処理をさせる様にします。

Public Class frmMulEventBtn

    Private Sub frmMulEventBtn_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 開始メッセージ出力ウインドウに表示
        Debug.Print("...Start")
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Button1 : " & Now.ToString("hh:mm:ss"))
        System.Threading.Thread.Sleep(1000 * 5)
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        ' 出力ウインドウに現在時刻表示
        Debug.Print("Button2 : " & Now.ToString("hh:mm:ss"))
    End Sub

End Class
...Start
Button1 : 02:40:41          ⇒この時点で「Button2」を3回クリック
Button2 : 02:40:46
Button2 : 02:40:46
Button2 : 02:40:46


Button1 をクリックした直後に Button2 を連続してクリックします。 Button2 をクリックしたすぐには時刻表示が無く Button1 のクリックイベント処理が終わった直後、 3個連続して時刻表示が行われます。
Button1 のイベント処理中には処理が他には移らないのですが、ボタンのクリックは覚えている様で連続して3回の時刻表示がされます。
これは結構問題で Button1 の処理が終わったと勘違いして Button2 の処理が行われない様にする必要があります。 解決方法としては、ボタンイベント処理中には全てのボタンをクリック不可の状態にするか、 もしくはフラグを導入してボタンクリック処理が連続して行わない様にするかではないでしょうか。

尚、キー入力などでも同様なことが起きるはずですので、この様な現象が起きない様に考える必要があります。

関連する記事

フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
フォーム上のコントロールのイベント処理の一括関連付けその2:[AddHandler,DirectCast]
コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
Timerコントロールの使い方        :[Timer,Interval]
複数のTimerコントロールの使い方     :[Timer,Interval]
コントロールを配列で処理する方法     :[AddHandler,DirectCast]











PR

コメント

コメントを書く