VB.NET で非同期デリゲートを使ったマルチスレッドの実装方法 [プログラミング]
VB.NETでマルチスレットを実装する方法はいくつかあるけれど、自分は「非同期デリゲート」を利用したマルチスレッドを利用している。
色々検討した結果、この方法が実行したスレッドの処理結果を把握しやすいので…
以下の例では"Thread1"と"Thread2"の二つの非同期処理を実行している。
"Thread1"は非同期処理が引数を必要としない場合で"Thread2"は非同期処理が引数を必要とする場合。
各非同期処理の戻り値は"THREAD_RETURN"構造体とすることで、処理の開始時刻、終了時刻、処理結果を取得可能にしている。
処理の流れは"Thread1"、"Thread2"共にほぼ同様で、大まかな流れは以下の通り。
1."fncThread[n]Start"プロシージャで非同期処理のデリゲートインスタンスを生成。
生成したインスタンスのBeginInvokeメソッドを呼び出し"fncThread[n]"を起動する。その際、コールバック関数として"subThread[n]Callback"を指定し、非同期処理終了時にコールバック関数で終了処理を行う。
※"Thread2"ではコールバック関数にも引数を渡している。(例では引数を取得するのみで利用していないが…)
2."fncThread[n]"プロシージャに非同期処理として実行したい処理を記述する。
3."subThread[n]Callback"プロシージャで非同期処理の終了処理を行う。
このプロシージャは"fncThread[n]"が終了すると呼び出されるが、非同期処理が完了しているかをデリゲートインスタンスのBeginInvokeメソッド呼び出し時に指定した戻り値"gobjThread[n]Return"のIsCompletedプロパティで確認する。もし完了していない場合は完了するまで待機する。
非同期処理の完了が確認できたらデリゲートインスタンスのEndInvokeメソッドを呼び出して非同期処理の戻り値を取得する。
そして、非同期処理終了時に実行したい処理を行った後、デリゲートインスタンスを破棄する。
以下の例を実行するには、新規の「コンソールアプリケーション」を作成し、コードを"Module1"に貼り付けて実行すれば良い。
コンソールウインドウが開き、各非同期処理が出力するメッセージが表示される。各非同期処理が終了するとメッセージボックスに各々の処理時間が出力される。
この例は非同期処理でマルチスレッドを実現する単純な処理だが、「Windowsフォームアプリケーション」で非同期処理を実行してその処理からフォームの内容を更新する場合にはスレッドセーフを意識したコードの記述が必要になる。
その方法についてはまた今度…。
------------------------------------------------------------
Module Module1
'非同期処理のデリゲート宣言
Delegate Function Thread1Delegate() As THREAD_RETURN
Delegate Function Thread2Delegate( _
ByVal pParam As Integer, _
ByVal pParam2 As Integer) As THREAD_RETURN
'型宣言
'スレッド戻り値
Public Structure THREAD_RETURN
Dim dStart As Date
Dim dEnd As Date
Dim blnState As Boolean
End Structure
'ユーザ定義変数
'非同期処理デリゲートインスタンス
Public gobjThread1Delegate As Thread1Delegate
Public gobjThread2Delegate As Thread2Delegate
'非同期処理戻り値
Public gobjThread1Return, gobjThread2Return _
As IAsyncResult
Public gtypReturn1, gtypReturn2 As THREAD_RETURN
'非同期処理実行中判定変数
Public gblnThread1Exec, gblnThread2Exec As Boolean
Sub Main()
If fncThread1Start() = False Then
MsgBox("非同期処理①の起動に失敗しました。", _
MsgBoxStyle.Exclamation)
End If
If fncThread2Start() = False Then
MsgBox("非同期処理②の起動に失敗しました。", _
MsgBoxStyle.Exclamation)
End If
Do While gblnThread1Exec = True OrElse _
gblnThread2Exec = True
System.Threading.Thread.Sleep(1000)
Loop
Dim oSpan As TimeSpan
Dim strMsg As String = _
"すべての非同期処理が終了しました。" & _
ControlChars.CrLf
With gtypReturn1
If .blnState = True Then
'非同期処理①が正常終了の場合
'処理時間を算出
oSpan = .dEnd - .dStart
'メッセージを編集
strMsg = strMsg & "Thread1処理時間:" & _
oSpan.Hours.ToString("00") & ":" & _
oSpan.Minutes.ToString("00") & ":" & _
oSpan.Seconds.ToString("00") & "." & _
oSpan.Milliseconds.ToString("000") & _
ControlChars.CrLf
Else
'非同期処理①が異常終了の場合
'メッセージを編集
strMsg = strMsg & _
"Thread1:異常終了" & ControlChars.CrLf
End If
End With
With gtypReturn2
If .blnState = True Then
'非同期処理②が正常終了の場合
'処理時間を算出
oSpan = .dEnd - .dStart
'メッセージを編集
strMsg = strMsg & "Thread2処理時間:" & _
oSpan.Hours.ToString("00") & ":" & _
oSpan.Minutes.ToString("00") & ":" & _
oSpan.Seconds.ToString("00") & "." & _
oSpan.Milliseconds.ToString("000")
Else
'非同期処理②が異常終了の場合
'メッセージを編集
strMsg = strMsg & "Thread2:異常終了"
End If
End With
MsgBox(strMsg, MsgBoxStyle.Information)
End Sub
Public Function fncThread1Start() As Boolean
'*************************
'非同期処理①開始処理
'*************************
Dim blnReturn As Boolean
Try
'非同期処理①実行中判定変数を設定
gblnThread1Exec = True
'非同期処理①のデリゲートインスタンスを生成
gobjThread1Delegate = New Thread1Delegate( _
AddressOf fncThread1)
'非同期処理①の非同期実行を開始
gobjThread1Return = _
gobjThread1Delegate.BeginInvoke( _
New AsyncCallback( _
AddressOf subThread1Callback), _
Nothing)
blnReturn = True
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
'非同期処理①実行中判定変数を設定
gblnThread1Exec = False
blnReturn = False
End Try
Return blnReturn
End Function
Public Function fncThread2Start() As Boolean
'*************************
'非同期処理②開始処理
'*************************
Dim blnReturn As Boolean
Try
'非同期処理②実行中判定変数を設定
gblnThread2Exec = True
'非同期処理②のデリゲートインスタンスを生成
gobjThread2Delegate = New Thread2Delegate( _
AddressOf fncThread2)
'非同期処理②の非同期実行を開始
'...BeginInvoke(1, 2, の"1, 2,"は"fncThread2"の
' 引数(pParam1、pParam2)に該当
'...subThread2Callback), 2 の"2"は
' "subThread2Callback"の引数に該当
gobjThread2Return = _
gobjThread2Delegate.BeginInvoke( _
1, 2, _
New AsyncCallback( _
AddressOf subThread2Callback), 2)
blnReturn = True
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
'非同期処理②実行中判定変数を設定
gblnThread2Exec = False
blnReturn = False
End Try
Return blnReturn
End Function
Public Function fncThread1() As THREAD_RETURN
'*************************
'非同期処理①
'引数を必要としない非同期処理
'*************************
Dim tReturn As THREAD_RETURN
Try
'非同期処理①の戻り値に開始時刻を設定
tReturn.dStart = Now
'ここに非同期で実行する処理を記述...(例えば)
Console.WriteLine("Thread1 started.")
System.Threading.Thread.Sleep(5000)
Console.WriteLine("Thread1 ended.")
'非同期処理①の戻り値に終了時刻、処理結果を設定
With tReturn
.dEnd = Now
.blnState = True
End With
Catch ex As Exception
'エラーが発生した場合
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
With tReturn
.dEnd = Now
.blnState = False
End With
End Try
Return tReturn
End Function
Public Function fncThread2(ByVal pParam1 As Integer, _
ByVal pParam2 As Integer) As THREAD_RETURN
'*************************
'非同期処理②
'引数を必要とする非同期処理
'*************************
Dim tReturn As THREAD_RETURN
Try
'非同期処理②の戻り値に開始時刻を設定
tReturn.dStart = Now
'ここに非同期で実行する処理を記述...(例えば)
Console.WriteLine("Thread2 started.")
Console.WriteLine("Thread2 Parameter1 is " & _
pParam1 & ".")
Console.WriteLine("Thread2 Parameter2 is " & _
pParam2 & ".")
System.Threading.Thread.Sleep(7000)
Console.WriteLine("Thread2 ended.")
'非同期処理②の戻り値に終了時刻、処理結果を設定
With tReturn
.dEnd = Now
.blnState = True
End With
Catch ex As Exception
'エラーが発生した場合
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
With tReturn
.dEnd = Now
.blnState = False
End With
End Try
Return tReturn
End Function
Public Sub subThread1Callback(ByVal ar As IAsyncResult)
'*************************
'非同期処理①のコールバック関数
'
' ar : 非同期処理情報インターフェース
'
'*************************
Try
Do
If gobjThread1Return.IsCompleted = True Then
'非同期処理①が完了した場合
Dim tReturn As THREAD_RETURN = _
gobjThread1Delegate.EndInvoke( _
gobjThread1Return)
If tReturn.blnState = False Then
'異常終了メッセージを表示
MsgBox( _
"非同期処理①で異常を検出しました。", _
MsgBoxStyle.Exclamation)
End If
'非同期処理①のデリゲートインスタンスを破棄
gobjThread1Delegate = Nothing
'非同期処理①の戻り値インスタンスを破棄
gobjThread1Return = Nothing
'非同期処理①実行中判定変数を設定
gblnThread1Exec = False
Exit Do
Else
'非同期処理①が実行中の場合
System.Threading.Thread.Sleep(250)
End If
Loop
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
End Try
End Sub
Public Sub subThread2Callback(ByVal ar As IAsyncResult)
'*************************
'非同期処理②のコールバック関数
'
' ar : 非同期処理情報インターフェース
'
'*************************
'ここでは利用しないけど、引数の取得方法を記述
Dim intParam As Integer = CType(ar.AsyncState, _
Integer)
Try
Do
If gobjThread2Return.IsCompleted = True _
Then
'非同期処理②が完了した場合
Dim tReturn As THREAD_RETURN = _
gobjThread2Delegate.EndInvoke(_
gobjThread2Return)
If tReturn.blnState = False Then
'異常終了メッセージを表示
MsgBox( _
"非同期処理②で異常を検出しました。", _
MsgBoxStyle.Exclamation)
End If
'非同期処理②のデリゲートインスタンスを破棄
gobjThread2Delegate = Nothing
'非同期処理②の戻り値インスタンスを破棄
gobjThread2Return = Nothing
'非同期処理②実行中判定変数を設定
gblnThread2Exec = False
Exit Do
Else
'非同期処理②が実行中の場合
System.Threading.Thread.Sleep(250)
End If
Loop
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
End Try
End Sub
End Module
------------------------------------------------------------
VB.NET でArray.FindIndexを使ったユーザ定義型配列の検索方法 [プログラミング]
単純な配列から指定した文字列をもつ要素のインデックスを検索する場合、"Array.FindIndex"を使って検索する方法がある。
方法は二通りあって、一つは検索条件を記述したメソッドを呼び出す方法で、もう一つはラムダ式を記述する方法だ。
メソッドを呼び出す方法はこんな感じ…
--------------------------------------------------------------------------------------------------------------
Private Function fncFindValue(ByVal pValue As String) As Boolean
'*************************************
'配列より特定の値をもつ要素を判定
'
' pValue : 配列の要素
'
'*************************************
Dim blnReturn As Boolean
'要素の値が指定値と一致するかを調べる
If pValue = "acdb" Then
blnReturn = True
Else
blnReturn = False
End If
Return blnReturn
End Function
Public Sub subFindArray
'*************************************
'配列より指定値と一致する要素のインデックスを取得
'*************************************
'配列を定義
Dim strArray() As String = {"abcd", "acdb", "adbc"}
'配列より指定値と一致する要素のインデクスを取得
Dim intIndex As Integer = Array.FindIndex(strArray, AddressOf fncFindValue)
MsgBox("'acdb'の値を持つ要素のインデックスは" & intIndex & "番目です。", MsgBoxStyle.Information)
End Sub
--------------------------------------------------------------------------------------------------------------
しかし、この方法だと検索できる値は"acdb"に限られてしまうので、"fncFindValue"で調べる値を指定するためには
"acbd"の代わりにグローバル変数を使うしかない。
但し、グローバル変数を使った場合は、プログラムがマルチスレッドだと"subFindArray"が複数のスレッドから呼び出されると
正しい答えが得られない可能性がある。
そんな場合はラムダ式を使うと回避できる。こんな感じ…
--------------------------------------------------------------------------------------------------------------
Public Sub subFindArray(ByVal pFindValue As String)
'*************************************
'配列より指定値と一致する要素のインデックスを取得
'
' pFindValue : 検索する値
'
'*************************************
'配列を定義
Dim strArray() As String = {"abcd", "acdb", "adbc"}
'配列より指定値と一致する要素のインデクスを取得
Dim intIndex As Integer = Array.FindIndex(strArray, Function(pArray As String)) _
pArray = pFindValue)
MsgBox("'" & pFindValue & "'の値を持つ要素のインデックスは" & intIndex & "番目です。", _
MsgBoxStyle.Information)
End Sub
--------------------------------------------------------------------------------------------------------------
つまり、呼び出すFunctionを直接記述してしまう感じなので、"subFindArray"の引数"pFindValue"は呼び出すスレッドが
指定した値だから他のスレッドの影響を受けることはない。
さて、ここまではFindIndexを使用した単純な配列の検索方法で、本題はここから…
自分はユーザ定義型を配列にして利用することが良くあるが、その配列から特定の値を持つ要素を検索するとなると
For ... Next を使った検索方法が最初に思い浮かぶが、ちょっとスマートじゃないし 、もっと簡単に検索したい。
そこでArray.FindIndexを利用しようと思ってヘルプやネットを検索しても前述のような単純な配列の例題しか
見つけることができなかった。
なので、ユーザ定義型配列の検索方法を残しておこうと思う…。
先ほどと同様にメソッドを呼び出す方法はこんな感じ…
--------------------------------------------------------------------------------------------------------------
'ユーザ定義型宣言
Public Structure USER_TYPE
Dim strValue As String
Dim intNo As Integer
End Structure
Private Function fncFindValue(ByVal pArray As USER_TYPE) As Boolean
'*************************************
'配列より特定の値をもつ要素を判定
'
' pArray : 配列の要素
'
'*************************************
Dim blnReturn As Boolean
'要素の値が指定値と一致するかを調べる
If pArray.strValue = "abcd" AndAlso pArray.intNo = 10 Then
blnReturn = True
Else
blnReturn = False
End If
Return blnReturn
End Function
Public Sub subFindArray(ByVal pFindValue As String, ByVal As pFindNo as Integer)
'*************************************
'配列より指定値と一致する要素のインデックスを取得
'
' pFindValue : 検索する文字列値
' pNo : 検索する数値
'
'*************************************
'ユーザ定義型の配列を定義
Dim udtArray(2) As USER_TYPE
'配列に値を設定
For i As Integer = 0 To typArray.Length - 1
udtArray(i) = New USER_TYPE
With udtArray(i)
.strValue = "abcd"
.intNo = i * 10
End With
Next i
'配列より指定値と一致する要素のインデクスを取得
Dim intIndex As Integer = Array.FindIndex(udtArray, AddressOf fncFindValue)
MsgBox("文字列'abcd'と数値'10'の値を持つ要素のインデックスは" & _
intIndex & "番目です。", MsgBoxStyle.Information)
End Sub
--------------------------------------------------------------------------------------------------------------
書いてみると「ごもっとも」って感じだが、ネット検索して例題を見つけたいときは動作が保証されたロジックが欲しいものだ。
単純な配列の時と異なるのは"fncFindValue"の中身。
この例ではメンバー"strValue"と"intNo"が共に一致する要素を判定している。
メンバーがもっと多数あってもっと複雑な条件でも応用できる。
さて、これをラムダ式で記述するとこんな感じになる。
--------------------------------------------------------------------------------------------------------------
'ユーザ定義型宣言
Public Structure USER_TYPE
Dim strValue As String
Dim intNo As Integer
End Structure
Public Sub subFindArray(ByVal pFindValue As String, ByVal As pFindNo as Integer)
'*************************************
'配列より指定値と一致する要素のインデックスを取得
'
' pFindValue : 検索する文字列値
' pNo : 検索する数値
'
'*************************************
'ユーザ定義型の配列を定義
Dim udtArray(2) As USER_TYPE
'配列に値を設定
For i As Integer = 0 To udtArray.Length - 1
udtArray(i) = New USER_TYPE
With udtArray(i)
.strValue = "abcd"
.intNo = i * 10
End With
Next i
'配列より指定値と一致する要素のインデクスを取得
Dim intIndex As Integer = Array.FindIndex(udtArray, Function(pArray As USER_TYPE)) _
pArray.strValue = pFindValue AndAlso _
pArray.intNo = pFindNo)
MsgBox("文字列'" & pFindValue & "'と数値'" & pFindNo & "'の値を持つ要素のインデックスは" & _
intIndex & "番目です。", MsgBoxStyle.Information)
End Sub
--------------------------------------------------------------------------------------------------------------
これもまた書いてみると「ごもっとも」だ。
ということで、この例題が誰かの役に立ってくれるとありがたいと思う。
ページ幅の関係等で読みづらくても許してください…。