読者です 読者をやめる 読者になる 読者になる

VB.NETで作る!

VB.NETに関するあれこれ

VB11.0のAsyncを使って処理遅延を監視する

タイトルにあるとおり、VB11.0(.NET Framework 4.5+Visual Studio 2012)が対象です。

f:id:mk3008net:20141214210001p:plain

概要

メイン処理が実行中かどうかを監視。想定時間内に終わらない場合は警告処理します。

イデア

  • メイン処理と並行して監視をする必要があるので、非同期の処理が必要
    → Asyncで対応する。
  • メイン処理が終わったら監視をする必要がないので、メイン処理終了判定が必要
    → Using(IDisposable)で判定する。
  • 警告処理
    → NLogを使う。

使い方

Private logger As NLog.Logger

Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles Me.Load
    Me.logger = NLog.LogManager.GetCurrentClassLogger
End Sub

Private Sub ExecuteButton_Click(sender As Object, e As EventArgs) Handles ExecuteButton.Click
    '5秒で終了する予定。終わらない場合、警告。
    '以降、3秒おきにチェック。終わっていない場合、警告。
    '遅延時間が30秒をを超えると致命的例外。
    Using w As New ProcessWatcher With {.ExpectTime = New TimeSpan(0, 0, 5),
                                        .WatchSpan = New TimeSpan(0, 0, 3),
                                        .MaxDelayTime = New TimeSpan(0, 0, 30)}

        'メイン処理
        logger.Info("Start")
        '遅延をメッセージボックスを使って擬似的に起こします
        MessageBox.Show("本当に実行しますか?")
        logger.Info("End")
    End Using
End Sub

ProcessWatcher が監視クラスです。同クラスが有効な期間(Using)が指定された時間に終わらないと警告、もしくは致命的例外がでます。

ProcessWatcherクラス

Public Sub New()
    Me.WatchingAsync()
End Sub

Private Async Sub WatchingAsync()
    Dim past As TimeSpan = TimeSpan.Zero

    Try
        Await Task.Run(
            Async Function()
                Do While True
                    If past.Equals(TimeSpan.Zero) Then
                        Await Task.Delay(Me.ExpectTime.TotalMilliseconds)
                        past = past.Add(Me.ExpectTime)
                    Else
                        Await Task.Delay(Me.WatchSpan.TotalMilliseconds)
                        past = past.Add(Me.WatchSpan)
                    End If

                    '廃棄されているなら正常終了
                    If Me.disposedValue Then Return

                    'メッセージの組み立て
                    Dim delay = past.Add(-Me.ExpectTime)
                    Dim msg As String = String.Empty
                    If delay.Equals(TimeSpan.Zero) Then
                        msg = String.Format("{0}経過", past.ToString("hh\:mm\:ss"))
                    Else
                        msg = String.Format("{0}経過({1}遅延)", past.ToString("hh\:mm\:ss"), delay.ToString("hh\:mm\:ss"))
                    End If

                    '遅延の上限値を超えた場合、致命的エラーを出して監視をやめる
                    If Me.WatchSpan.Equals(TimeSpan.Zero) OrElse Me.MaxDelayTime <= delay Then
                        msg &= ControlChars.NewLine & "遅延時間の閾値を超えました。監視を中止します。"
                        Throw New InvalidOperationException(msg)
                    End If

                    '遅延状況を警告で出力
                    logger.Warn(msg)
                Loop
            End Function)
    Catch ex As Exception
        logger.Fatal(ex.Message, ex)
    End Try
End Sub

プロパティやIDisposableの実装部分は省略しています。 やってることは非常に単純で、コンストラクタで非同期で監視処理をコールしているだけ。

備考

自分自身で実行かつ監視しているので、そもそも起動していない可能性を監視することはできません。当たり前ですけど。
また、中止するのは「監視処理」のみで、「メイン処理」を止めることはできません。

参考サイト

Async および Await を使用した非同期プログラミング (C# および Visual Basic)

.NET開発における非同期処理の基礎と歴史 − @IT

. .