その1はこちら
やまたのおろちが酒をのむ・リターンズ~片桐的マルチスレッドVBプログラミング~
さて、ここで、スサノオさんがやってきて、やまたのおろちさんの首を落とすことにするw
Imports System
Imports System.Threading
Module OrochMTA
Delegate Sub DrinkingSAKE()
Delegate Sub CuttingHead()
'酒ツボに酒を準備
Public inSakeTubo As New SakeTubo(2000)
<MTAThread()> _
Public Sub Main()
'本体で首が別々に酒飲みするのを身構える
Dim ts As New ThreadStart(AddressOf OrochiDrinking)
Dim workerThread As New Thread(ts)
'首は別々に動けるように、マルチスレッドで宣言
workerThread.SetApartmentState(ApartmentState.MTA)
workerThread.Start()
workerThread.Join()
Stop
End Sub
Private Sub OrochiDrinking()
Try
'まずは首を八本で飲んでみる
Dim headCount As Integer = 8
'酔っ払い待ち行列を首の数だけ準備
Dim yoppa(headCount - 1) As WaitHandle
'酒飲み開始
For i As Integer = 1 To headCount
'飲んだ首から酔っ払いへと
yoppa(i - 1) = HeadDrinking(i).AsyncWaitHandle
Next
'全部の首が酔っ払いになるまで待機
WaitHandle.WaitAll(yoppa)
Catch ex As Exception
Console.WriteLine("首切られた!")
End Try
End Sub
Private Function HeadDrinking(ByVal headNo As Integer) As IAsyncResult
'指定した番号の首に酒を飲ませる
Dim myHead As New OrochiHead(headNo)
Dim gubi As New DrinkingSAKE(AddressOf myHead.Drink)
'酒飲み開始とともに、酔っ払い待ち行列へ戻す
Return gubi.BeginInvoke(Nothing, Nothing)
End Function
Public Class OrochiHead
'おろちの首クラス
'お前は何番目なのかを保持
Private meNumber As Integer
'何番目の首?初期化w
Public Sub New(ByVal headNo As Integer)
meNumber = headNo
End Sub
'酒を飲むメソッド
Public Sub Drink()
Dim nowDrink As Integer
Dim nowNokori As Integer
Dim oneDrink As New Random
Dim oneBreath As New Random
'酒ツボの中の酒を飲み干す
While Not (inSakeTubo.Nomihoshited)
'一回飲むたびに息継ぎ
Thread.Sleep(oneBreath.Next(30, 60))
'酒つぼから飲んでみる
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「もう酒がねぇよ!!ぷんすか」", meNumber)
Exit Sub
Else
'一回ぐび
nowDrink = oneDrink.Next(1, 100)
nowNokori = inSakeTubo.Nokori
inSakeTubo.Gubi(nowDrink)
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「ちきしょう!足りねぇよ!{1}のみてぇのに{2}しかねぇ!」", meNumber, nowDrink, nowNokori)
Exit Sub
Else
Console.WriteLine("{0}番目の首が {1} リットル ぐびっ : 残り {2} リットル", meNumber, nowDrink, inSakeTubo.Nokori)
End If
End If
'スサノオさんアップ中
Dim cutNo As New Random
Dim cutHead = cutNo.Next(1, 8)
'運が悪かったら首切りw
If cutHead = meNumber Then
Console.WriteLine("スサノオさんが{0}番目の首切った!", meNumber)
Throw New Exception("やまたのおろちさんえくせぷしょん")
End If
End While
Console.WriteLine("ぷはーっ! {0}番目の首、飲み終わり!", meNumber)
End Sub
End Class
Public Class SakeTubo
'酒つぼクラス
'マルチスレッドでの読み書き保護処理を集約
Private cacheLock As New ReaderWriterLockSlim()
Private innerSake As Integer
Public Sub New(ByVal litter As Integer)
innerSake = litter
End Sub
Public Function Nokori() As Integer
cacheLock.EnterReadLock()
Try
Return innerSake
Finally
cacheLock.ExitReadLock()
End Try
End Function
Public Sub Gubi(ByVal gubigubi As Integer)
Dim retFlag As Boolean = True
cacheLock.EnterWriteLock()
Try
If innerSake > gubigubi Then
innerSake -= gubigubi
Else
innerSake = 0
End If
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Sub Karappo()
cacheLock.EnterWriteLock()
Try
innerSake = 0
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Function Nomihoshited() As Boolean
cacheLock.EnterWriteLock()
Try
Return (innerSake.Equals(0))
Finally
cacheLock.ExitWriteLock()
End Try
End Function
End Class
End Module
前回とあまり変わってないけど、おろちクラスの中で、運が悪いとスサノオさんに首を切られてExceptionが発生します。
結果はこんな感じ
あれ?例外発生してない????
実は、マルチスレッドで動作している中の一つの子スレッドが例外発生して止まっても他の兄弟姉妹スレッドは動き続けるし、スレッドの発生元である親スレッドでその例外をCatchすることができない。だから、実行中に複数のうちの一つの子スレッドでエラーが発生しても見た目には判んないことがあるの。これはとっても大変なこと。
で、私はこれをどうしたかというと、おろちの首が落ちたかどうかを首クラスに情報として持たせておいて、全てのスレッドが終了した後で判定する方法をとってみることにしたの。
Imports System
Imports System.Threading
Module OrochMTA
Delegate Sub DrinkingSAKE()
'酒ツボに酒を準備
Public inSakeTubo As New SakeTubo(2000)
'おろちの首準備
Public orochiHeads As New List(Of OrochiHead)
<MTAThread()> _
Public Sub Main()
'本体で首が別々に酒飲みするのを身構える
Dim ts As New ThreadStart(AddressOf OrochiDrinking)
Dim workerThread As New Thread(ts)
'首は別々に動けるように、マルチスレッドで宣言
workerThread.SetApartmentState(ApartmentState.MTA)
workerThread.Start()
workerThread.Join()
Stop
End Sub
Private Sub OrochiDrinking()
'まずは首を八本で飲んでみる
Dim headCount As Integer = 8
'酔っ払い待ち行列を首の数だけ準備
Dim yoppa(headCount - 1) As WaitHandle
orochiHeads.Capacity = headCount
'酒飲み開始
For i As Integer = 1 To headCount
'飲んだ首から酔っ払いへと
yoppa(i - 1) = HeadDrinking(i).AsyncWaitHandle
Next
'全部の首が酔っ払いになるまで待機
WaitHandle.WaitAll(yoppa)
'切られた首チェック
For Each head In orochiHeads
If head.orochiException Is Nothing Then
Continue For
End If
Console.WriteLine(head.orochiException.Message)
Next
End Sub
Private Function HeadDrinking(ByVal headNo As Integer) As IAsyncResult
'指定した番号の首に酒を飲ませる
orochiHeads.Add(New OrochiHead(headNo))
Dim gubi As New DrinkingSAKE(AddressOf orochiHeads(headNo - 1).Drink)
'酒飲み開始とともに、酔っ払い待ち行列へ戻す
Return gubi.BeginInvoke(Nothing, Nothing)
End Function
Public Class OrochiHead
'おろちの首クラス
'首が無事なのかどうか
Public orochiException As Exception
'お前は何番目なのかを保持
Private meNumber As Integer
'何番目の首?初期化w
Public Sub New(ByVal headNo As Integer)
meNumber = headNo
End Sub
'酒を飲むメソッド
Public Sub Drink()
Dim nowDrink As Integer
Dim nowNokori As Integer
Dim oneDrink As New Random
Dim oneBreath As New Random
'酒ツボの中の酒を飲み干す
While Not (inSakeTubo.Nomihoshited)
'一回飲むたびに息継ぎ
Thread.Sleep(oneBreath.Next(30, 60))
'酒つぼから飲んでみる
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「もう酒がねぇよ!!ぷんすか」", meNumber)
Exit Sub
Else
'一回ぐび
nowDrink = oneDrink.Next(1, 100)
nowNokori = inSakeTubo.Nokori
inSakeTubo.Gubi(nowDrink)
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「ちきしょう!足りねぇよ!{1}のみてぇのに{2}しかねぇ!」", meNumber, nowDrink, nowNokori)
Exit Sub
Else
Console.WriteLine("{0}番目の首が {1} リットル ぐびっ : 残り {2} リットル", meNumber, nowDrink, inSakeTubo.Nokori)
End If
End If
'スサノオさんアップ中
Dim cutNo As New Random
Dim cutHead = cutNo.Next(1, 8)
'運が悪かったら首切りw
If cutHead = meNumber Then
Console.WriteLine("スサノオさんが{0}番目の首切った!", meNumber)
orochiException = New Exception(String.Format("{0}番目の首は切られてしまった!", meNumber))
Exit Sub
End If
End While
Console.WriteLine("ぷはーっ! {0}番目の首、飲み終わり!", meNumber)
End Sub
End Class
Public Class SakeTubo
'酒つぼクラス
'マルチスレッドでの読み書き保護処理を集約
Private cacheLock As New ReaderWriterLockSlim()
Private innerSake As Integer
Public Sub New(ByVal litter As Integer)
innerSake = litter
End Sub
Public Function Nokori() As Integer
cacheLock.EnterReadLock()
Try
Return innerSake
Finally
cacheLock.ExitReadLock()
End Try
End Function
Public Sub Gubi(ByVal gubigubi As Integer)
Dim retFlag As Boolean = True
cacheLock.EnterWriteLock()
Try
If innerSake > gubigubi Then
innerSake -= gubigubi
Else
innerSake = 0
End If
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Sub Karappo()
cacheLock.EnterWriteLock()
Try
innerSake = 0
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Function Nomihoshited() As Boolean
cacheLock.EnterWriteLock()
Try
Return (innerSake.Equals(0))
Finally
cacheLock.ExitWriteLock()
End Try
End Function
End Class
End Module
こんな感じ。いままでSub内部のローカル関数だったおろちの首クラスをもっと広いスコープの変数に引っ越してLISTにしておいて(Line.11)、首クラスで情報が保持できるように変更しておき(Line.83)、全ての子スレッドが終わった後で首が無事だったかどうかを判定することで子スレッドの例外を親側で検知(Line.55)って流れ。
このロジックの場合、全ての処理が終わらないと例外の有無が判定できないので、時間のかかる処理だとちょっとストレスがあるかもしれない。安全なアプリケーションの終了方法ではあるけれどもね。