VB2008: 2022年5月アーカイブ

前回は AsyncWaitHandle.WaitOne メソッドで非同期デリゲート処理の終了待ちをしましたので、今回は、IsCompleted プロパティで終了待ちを試してみる事に。(UIサービスを提供するスレッド向き?)

★元ネタ-マイクロソフトドキュメントで、ほぼそのままのソース。


<注意点>

・非同期呼び出しの完了を IsCompleted でポーリングすると、スレッドプールで非同期処理を実行しながら、呼出し元スレッドを実行継続できます。

・何れの終了待ちをするにしても、EndInvokeを常に呼び出して、非同期呼出しの完了とします。 ← 重要!

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

Button3 thread 10 does some work.
Wait Test begins.
.....
.....
.....
.....
.....
.....
.....
.....
The call executed on thread 7, with return value "Wait time was 2000.".

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form1

    Private Delegate Function dlgtAsyncWaitChk(ByVal tm As Integer, ByRef id As Integer) As String

    Private Function waitTest(ByVal term As Integer, ByRef threadId As Integer) As String
        Debug.Print("Wait Test begins.")
        System.Threading.Thread.Sleep(term)
        threadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        Return String.Format("Wait time was {0}.", term.ToString())
    End Function

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        Dim threadId As Integer
        Dim awc As dlgtAsyncWaitChk
        Dim result As IAsyncResult
        Dim retValue As String

        awc = New dlgtAsyncWaitChk(AddressOf waitTest)
        result = awc.BeginInvoke(2000, threadId, Nothing, Nothing)

        Debug.Print("Button3 thread {0} does some work.", System.Threading.Thread.CurrentThread.ManagedThreadId)

        While Not result.IsCompleted
            System.Threading.Thread.Sleep(250)
            Debug.Print(".....")
        End While

        retValue = awc.EndInvoke(threadId, result)
        Debug.Print("The call executed on thread {0}, with return value ""{1}"".", threadId, retValue)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

前回は EndInvoke メソッドで非同期デリゲート処理の終了待ちをしましたので、今回は、AsyncWaitHandle プロパティの WaitOne メソッドで終了待ちを試してみる事に。(当然、UIスレッドには不向き)。

★元ネタ-マイクロソフトドキュメントで、ほぼそのままのソース。


<注意点>

・WaitHandle は非同期呼び出しが完了すると通知されるので、WaitOne 呼び出しでこれを待機します。

・待機ハンドルの使用終了と同時にシステムリソースを解放する場合、WaitHandle.Close メソッドを呼び出します。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

Button2 thread 10 does some work.
Wait Test begins.
The call executed on thread 7, with return value "Wait time was 2000.".

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form1

    Private Delegate Function dlgtAsyncWaitChk(ByVal tm As Integer, ByRef id As Integer) As String

    Private Function waitTest(ByVal term As Integer, ByRef threadId As Integer) As String
        Debug.Print("Wait Test begins.")
        System.Threading.Thread.Sleep(term)
        threadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        Return String.Format("Wait time was {0}.", term.ToString())
    End Function

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim threadId As Integer
        Dim awc As dlgtAsyncWaitChk
        Dim result As IAsyncResult
        Dim retValue As String

        awc = New dlgtAsyncWaitChk(AddressOf waitTest)
        result = awc.BeginInvoke(2000, threadId, Nothing, Nothing)

        Debug.Print("Button2 thread {0} does some work.", System.Threading.Thread.CurrentThread.ManagedThreadId)

        result.AsyncWaitHandle.WaitOne()

        retValue = awc.EndInvoke(threadId, result)

        result.AsyncWaitHandle.Close() ' ガベージコレクションの効率化
        Debug.Print("The call executed on thread {0}, with return value ""{1}"".", threadId, retValue)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

非同期デリゲート処理の呼び出し完了待ちを検索していたら、マイクロソフトが言うには、

・呼び出すメソッドと同じシグネチャを持つデリゲートを定義すれば、.NETでは全てのメソッドを非同期的に呼び出すことが出来る。

・デリゲート定義では、共通言語ランタイム(CLR)により適切なシグネチャが使用された BeginInvoke および EndInvoke メソッドが自動的に定義される。

・BeginInvoke メソッドは、非同期的に実行するメソッドと同じパラメーターと、2つの省略可能な追加パラメーターを持っている。
 追加の1つ目は、非同期呼び出しが完了したときに呼び出されるメソッドを参照する AsyncCallback デリゲート
 追加の2つ目は、コールバックメソッドに情報を渡すユーザー定義オブジェクト。

・BeginInvoke は IAsyncResult を返すので、これを使用して非同期呼び出しの進捗状況を監視出来る。

・EndInvoke メソッドは、非同期処理の呼び出し結果を取得し、BeginInvoke の後であればいつでも呼び出すことが出来る。

・EndInvoke のパラメーターには、非同期実行するメソッドの <Out>ByRef と ByRef、BeginInvoke の戻り値 IAsyncResult が含まれる。

らしいので、追加パラメーター2つを省略して(=Nothing)、EndInvokeメソッドで非同期処理の終了待ちを試してみる事に(当然、UIスレッドには不向き)。

★元ネタ-マイクロソフトドキュメントで、ほぼそのままのソース。


<注意点>

・非同期呼び出しが完了していない場合、 EndInvoke は非同期呼び出しが完了するまで呼び出し元スレッドをブロック。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

Wait Test begins.
Button1 thread 10 does some work.
The call executed on thread 7, with return value "Wait time was 2000.".

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form1

    Private Delegate Function dlgtAsyncWaitChk(ByVal tm As Integer, ByRef id As Integer) As String

    Private Function waitTest(ByVal term As Integer, ByRef threadId As Integer) As String
        Debug.Print("Wait Test begins.")
        System.Threading.Thread.Sleep(term)
        threadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        Return String.Format("Wait time was {0}.", term.ToString())
    End Function

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim threadId As Integer
        Dim awc As dlgtAsyncWaitChk
        Dim result As IAsyncResult
        Dim retValue As String

        awc = New dlgtAsyncWaitChk(AddressOf waitTest)
        result = awc.BeginInvoke(2000, threadId, Nothing, Nothing)

        Debug.Print("Button1 thread {0} does some work.", System.Threading.Thread.CurrentThread.ManagedThreadId)

        retValue = awc.EndInvoke(threadId, result)
        Debug.Print("The call executed on thread {0}, with return value ""{1}"".", threadId, retValue)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

フリーズしてる訳でもないんだから、プログレスバーでも出しておけば良いんじゃないの?と書いておきながら、非同期処理でUIにプログレスバーを表示してみる事に。

矢張り曲者は、別スレッドからのUIコントロール更新でしょうか。(デリゲートメソッドもコールバックメソッドも、スレッドプールで処理を実行)

【デリゲートメソッドの終了待ち】

・BeginInvoke呼出しメソッドの終了をEndInvokeで受けるのですが、UI(メイン)スレッド中に EndInvoke を呼び出すと非同期(デリゲート)メソッドが完了するまで、呼出し元スレッドがブロックされてしまいます。*1

・UIサービスを提供するスレッドからは、EndInvoke を呼び出さない方が宜しいかと。⇒ コールバックメソッドの利用

*1:IDEのデバッグ実行では、飛んだまま戻ってこず


<注意点>

・仮引数の範囲のチェック(0%~100%のプログレスバー設定値)や何処でチェックするかなどは、多分に好みの問題だと思うので・・・。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

・ボタン1を押すと、

プログレスバー表示その1

プログレスバー表示その2

・ボタン2を押すと、

プログレスバー表示その3

プログレスバー表示その4

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form4

    Private Delegate Sub dlgtWork1Prgbar1(ByVal data As Integer)

    Private Sub Set1Prgbar1Value(ByVal percent As Integer)
        If InvokeRequired = True Then
            Invoke(New dlgtWork1Prgbar1(AddressOf Set1Prgbar1Value), percent)
        Else
            ProgressBar1.Value = percent
            If percent >= 100 Then
                Label1.ForeColor = System.Drawing.Color.Red
                Label1.Text = "完了"
                Button1.Enabled = True
            End If
        End If
    End Sub

    Private Sub work1PrgBar1(ByVal no As Integer)
        For i As Integer = no To 10
            System.Threading.Thread.Sleep(500) ' 時間の掛かる処理の代替
            Set1Prgbar1Value(i * 10)
        Next
    End Sub

    Private Sub bar1Callback1(ByVal ar As IAsyncResult)
        Dim arWk1Prgbar1 As dlgtWork1Prgbar1

        arWk1Prgbar1 = CType(ar.AsyncState, dlgtWork1Prgbar1)
        arWk1Prgbar1.EndInvoke(ar)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim wk1PrgBar1 As dlgtWork1Prgbar1
        Dim ar As IAsyncResult

        Button1.Enabled = False
        Label1.ForeColor = System.Drawing.Color.FromKnownColor(KnownColor.ControlText)
        Label1.Text = "実行中・・・"

        Set1Prgbar1Value(0)
        wk1PrgBar1 = New dlgtWork1Prgbar1(AddressOf work1PrgBar1)
        ar = wk1PrgBar1.BeginInvoke(1, New AsyncCallback(AddressOf bar1Callback1), wk1PrgBar1)
    End Sub

/*--------------------------  お試しソース 改 -----------------------------*/

    Structure prgrsData
        Dim percent As Integer
        Dim leftTm As TimeSpan
    End Structure

    Private Delegate Sub dlgtWork2Prgbar1(ByVal data As prgrsData)

    Private Sub Set2Prgbar1Value(ByVal prgDat As prgrsData)
        If InvokeRequired = True Then
            Invoke(New dlgtWork2Prgbar1(AddressOf Set2Prgbar1Value), prgDat)
        Else
            ProgressBar1.Value = prgDat.percent
            Label1.Text = String.Format("あと {0:0.0} 秒・・・", prgDat.leftTm.TotalSeconds)
            If prgDat.percent >= 100 Then
                Label1.ForeColor = System.Drawing.Color.Red
                Label1.Text = "完了"
                Button2.Enabled = True
            End If
        End If
    End Sub

    Private Sub work2PrgBar1(ByVal prgDat As prgrsData)
        Const Cntr As Integer = 10
        Const MSec As Integer = 500
        Dim pData As prgrsData

        For i As Integer = prgDat.percent To Cntr
            System.Threading.Thread.Sleep(MSec) ' 時間の掛かる処理の代替
            pData.percent = i * Cntr
            pData.leftTm = TimeSpan.FromMilliseconds((Cntr - i) * MSec)
            Set2Prgbar1Value(pData)
        Next
    End Sub

    Private Sub bar1Callback2(ByVal ar As IAsyncResult)
        Dim arWk2Prgbar1 As dlgtWork2Prgbar1

        arWk2Prgbar1 = CType(ar.AsyncState, dlgtWork2Prgbar1)
        arWk2Prgbar1.EndInvoke(ar)
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim wk2Prgbar1 As dlgtWork2Prgbar1
        Dim ar As IAsyncResult
        Dim progDat As prgrsData

        Button2.Enabled = False
        Label1.ForeColor = System.Drawing.Color.FromKnownColor(KnownColor.ControlText)
        Label1.Text = "実行中・・・"

        progDat.percent = 0
        progDat.leftTm = TimeSpan.FromMilliseconds(5000)
        Set2Prgbar1Value(progDat)
        progDat.percent = 1
        wk2Prgbar1 = New dlgtWork2Prgbar1(AddressOf work2PrgBar1)
        ar = wk2Prgbar1.BeginInvoke(progDat, New AsyncCallback(AddressOf bar1Callback2), wk2Prgbar1)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

このアーカイブについて

このページには、2022年5月以降に書かれたブログ記事のうちVB2008カテゴリに属しているものが含まれています。

前のアーカイブはVB2008: 2022年4月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

お気に入りリンク

NOP法人 アジアチャイルドサポート 最も大切なボランティアは、自分自身が一生懸命に生きること