[2019/06/11] プログラムの多重起動の抑止(Mutexを使った方法) (No.102)
[2019/05/09] スレッドを停止させる方法について (No.97)
[2019/05/08] 引数があるスレッドの実行について(パラメータ付きスレッド) (No.96)
[2019/04/26] 名前付きパイプを使ったプロセス間通信についてその3(クライアントとの双方向通信) (No.95)
[2019/04/24] 名前付きパイプを使ったプロセス間通信についてその2(複数クライアントとの通信) (No.94)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
プログラムの多重起動を抑止する方法(自分自身が1回しか起動されないようにする方法)について説明します。 抑止する方法はいろいろあるのですが Mutex を使った方法が比較的簡単だと思いますので、今回はその方法について記します。
先ずは Mutex についてですが、これは排他制御を行う為の仕組みで、現在それを「使用中」か「未使用」かを示すフラグの様なものです。 あるプロセス(実行プログラム)が、ある Mutex を取得しそのフラグを「使用中」にします。 再度そのプロセスを実行した場合にその Mutex は既にフラグが「使用中」であれば、起動を中止する様にします。 (簡単な「セマフォ」であるとも言えます)
.NET には Mutex クラスがありますのでそれを使うことになります。Mutex クラスについて
<コンストラクタ> System.Threading.Mutex(initiallyOwned As Boolean, name As String, ByRef createdNew As Boolean) ・initiallyOwned:名前付きシステム ミューテックスの初期所有権を付与する場合は true。 それ以外の場合は false。 ・name :Mutex の名前です。 ・createdNew :指定した名前付きシステム ミューテックスが作成された場合はブール値 true が格納されます。 指定した名前付きシステム ミューテックスが既に存在する場合は false が格納されます。
Mutexを使ったプログラムは以下の様になります。 上記の Mutex クラスを生成した結果 createdNew フラグを確認することで Mutex が生成されたかが分かります。 true でなければ実際の処理は行わない様にして処理を抜ける様にします。
Mutexを使ったプログラムの多重起動の抑止
Module MdlMain 'Main関数 Sub Main() 'ミューテックス名(一意な名前にすること) Dim mstrMutexName As String = "TestProgramName" '(仮の名前) 'ミューテックス Dim mMutex As System.Threading.Mutex = Nothing '新規作成フラグ Dim blnCreated As Boolean = False Try Try '名前付きシステム ミューテックスの初期所有権を付与 mMutex = New System.Threading.Mutex(True, mstrMutexName, blnCreated) Catch ex As Exception MessageBox.Show("多重起動はできません。Mutex Error[" & ex.Message & "]") Return End Try 'ミューテックスが作成されたか調べる If blnCreated = False Then MessageBox.Show("多重起動はできません。") Return End If '既設のMainメソッドの処理を実行(表示させたいフォームを表示) Dim frm As New frmTest Application.Run(frm) Finally If blnCreated Then 'ミューテックスを解放する mMutex.ReleaseMutex() End If mMutex.Close() End Try End Sub End Module
プログラム中の frmTest は通常のフォームを作成しただけで、フォームの動きは特にプログラミングしていません。
実行の様子を下図に示します。作成された実行ファイルを起動し、その後同じものを起動した時の様子です。
尚、今回の例では Sub Main() メソッドを開始指定(エントリポイント指定)をしてやるのですが、 「プロジェクト・プロパティ」の中の「アプリケーション」タブにある「スタートアップフォーム」でエントリポイントを指定するのですが、 このままでは Sub Main() が中に表示されません。
そこで「アプリケーションフレームワークを有効にする」チェックボックスを無効にすると 「スタートアップフォーム」は「スタートアップオブジェクト」の表示となり、 Sub Main() が出てきますので、それを指定します。
PR -
スレッドを以下のプログラムの様に作成し開始させると Main 関数は先に終了しますが、スレッドは約5秒後にしか終了しないため、 全体のプログラムの実行はスレッドが終了するまで続きます。
コンソールへの表示からも分かりますが「Main終了」が表示された後で「...スレッド」が5回表示されます。
これは何もおかしいことでは無く、プログラムは属する全てのスレッドが終了するまでは終われないのです。
Main 関数も1個のスレッド(メインスレッド)として実行されるのでが処理的に先に終わってしまいます。
しかし、別のスレッドとして起動された ThreadProc 関数が残っているので、このスレッドが終了するまで プログラムは終了しません。スレッドの実行プログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
取敢えず、実行結果がコンソールに以下の様に表示されます。
スレッド開始 Main終了 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド
それでは Main 関数の中でスレッドの終了を待つにはどうすればいいのでしょうか。 そのために Thread クラスには Join メソッドがあります。 この Join をスレッドの実行側で呼出せば、呼び出し側の処理がブロックされます。
(Join 実行した場合に制御が戻ってこない)
先ほどの例に Join を追加した例を示します。
スレッドの実行を待つプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
実行結果は以下の様に「Main終了」が最後に表示されます。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
Join の替わりにスレッドが生きているかどうかのフラグを見て以下の様にしても同じです。
スレッドの実行を待つプログラム2
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 While pThread.IsAlive = True Threading.Thread.Sleep(10) End While Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
それでは標題にある様にスレッドを停止させる方法についてですが、 Abort メソッドを使用します。 スレッドの中で非常に時間の掛かる処理を行わせて、途中で停止させる例を以下に示します。スレッドの実行を途中で停止させるプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pThread.Abort() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 1000 '時間の掛かる処理 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
実行結果は以下の様になり「...スレッド」が10回表示されてからスレッドが停止していることが分かります。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
スレッドをいきなり終了させるのも怖いので、スレッドに対してフラグで指示を行い、 スレッドの中でフラグを見て終了させるようにしてみます。
尚、スレッドを含む処理をクラスの中に入れ込んで、 その中で開始メソッドや停止メソッドを作成します。スレッドの実行を途中で停止させるクラスの導入
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド用クラスの生成 Dim pclsThread As New clsThread 'スレッド開始 pclsThread.Start() Console.WriteLine("スレッド開始") '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pclsThread.Abort() Console.WriteLine("Main終了") End Sub 'スレッド用クラス Class clsThread 'スレッド Private mThread As Thread = Nothing 'スレッド停止フラグ Private mblnStop As Boolean = False '開始メソッド Sub Start() mThread = New Thread(AddressOf ThreadProc) 'スレッド開始 mThread.Start() End Sub '停止メソッド Sub Abort() 'スレッド停止フラグON mblnStop = True 'スレッド停止を待つ mThread.Join() End Sub 'スレッド関数 Private Sub ThreadProc() For i As Integer = 1 To 1000 If Me.mblnStop = True Then 'スレッド停止フラグONならば処理中断 Exit For End If Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Class End Module
-
スレッドの関数に引数を渡して別々の処理をさせたい場合があると思いますが、 スレッドを生成する時にアドレス指定のところに ParameterizedThreadStart デリゲートを使うことで可能です。
以下に実行プログラムの例を示します。引数があるスレッドの実行プログラム
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() '[ParameterizedThreadStart]デリゲートを使ったスレッド生成 Dim pThread As Thread = New Thread(New ParameterizedThreadStart(AddressOf ThreadParam)) '(ParameterizedThreadStart)の指定が無くてもOKですが 'Dim pThread As Thread = New Thread(AddressOf ThreadParam) 'スレッド開始 pThread.Start("スレッド...") Console.WriteLine("スレッド開始") End Sub '引数を持つスレッド関数(引数は1個のオブジェクトのみ) Sub ThreadParam(ByVal obj As Object) For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine(obj.ToString()) Next End Sub End Module
取敢えず、実行結果がコンソールに以下の様に表示されます。
スレッド開始 スレッド... スレッド... スレッド... スレッド... スレッド...
ParameterizedThreadStart の利用は引数が1個なので複数のデータを渡す場合には 配列等(リストオブジェクト等)にして、スレッド関数側で配列の内容について処理を規定する必要があります。
以下に引数をオブジェクトの配列として渡す場合の例を示します。引数があるスレッドの実行プログラム2
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() '[ParameterizedThreadStart]デリゲートを使ったスレッド生成 Dim pThread As Thread = New Thread(New ParameterizedThreadStart(AddressOf ThreadParam)) 'オブジェクト配列データ設定 Dim arrObj(2) As Object arrObj(0) = "1111" arrObj(1) = 12345 arrObj(2) = True 'スレッド開始 pThread.Start(arrObj) Console.WriteLine("スレッド開始") End Sub '引数を持つスレッド関数(引数は1個のオブジェクトのみ) Sub ThreadParam(ByVal obj As Object) '引数をオブジェクト配列として扱う Dim pArr As Object() = CType(obj, Object()) For i As Integer = 0 To pArr.Length - 1 Threading.Thread.Sleep(1000) Console.WriteLine(pArr(i).ToString()) Next End Sub End Module
引数にして渡さなくても、スレッド実行をラップする様なクラスを導入して コンストラクタの引数でデータ型を規定することもできます。
以下に、スレッドを実行するためのクラスを導入した例を示します。スレッドの引数としてクラスでラップした実行プログラム
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() 'スレッド用クラスの生成 Dim pclsThread As New clsThread(12345, "AAA", False) 'スレッド開始 pclsThread.Start() Console.WriteLine("スレッド開始") End Sub 'スレッド用クラス Class clsThread 'クラス生成時に退避されるデータ群 Private mintData As Integer Private mstrData As String Private mblnData As Boolean 'コンストラクタ Sub New(ByVal aintData As Integer, ByVal astrData As String, ByVal ablnData As Boolean) Me.mintData = aintData Me.mstrData = astrData Me.mblnData = ablnData End Sub '内部スレッドの開始メソッド Sub Start() Dim pThread As Thread = New Thread(AddressOf ThreadProc) 'スレッド開始 pThread.Start() End Sub 'スレッド関数(この関数はプライベート) Private Sub ThreadProc() Threading.Thread.Sleep(1000) Console.WriteLine("Integer Data = " & Me.mintData) Threading.Thread.Sleep(1000) Console.WriteLine("String Data = " & Me.mstrData) Threading.Thread.Sleep(1000) Console.WriteLine("Boolean Data = " & Me.mblnData) End Sub End Class End Module
実行結果は以下の様に表示されます。
スレッド開始 Integer Data = 12345 String Data = AAA Boolean Data = False
スレッドを含む処理をクラスの中に入れ込んで、 その中で開始や停止などを行った方がスレッド処理を呼出す側との処理の分離がうまくいくと思います。
-
名前付きパイプを使った複数クライアントプログラムとの通信の説明は以下の記事で行いましたが、 今回はこれにクライアントとの双方向通信が出来る様に変更してみました。
⇒名前付きパイプを使ったプロセス間通信についてその2(複数クライアントとの通信)
双方向と言っても、クライアント側からサーバー側に送信されてきた文字列に、応答の為に少しの文字列を付加してクライアント側に送信する処理を考えます。
サーバー側の変更個所は以下の通りです。- 通信スレッドの中でストリーム読込を生成を行った後で、ストリーム書込を生成します。
- 処理ループの中でストリーム読込を行った後で、受信した文字列に「...OK」の文字列を付加してストリーム書込を行います。
サーバー側プログラム
Imports System.Threading Imports System.IO Imports System.IO.Pipes Public Class frmNameServer '別スレッドからメッセージを処理するためデリゲートを利用 Delegate Sub SetRichTextBox1Delegate(ByVal Value As String) Private RichTextBox1Delegate As New SetRichTextBox1Delegate(AddressOf _ AppendTextRichTextBox1) 'リッチテキストボックスにメッセージを表示する Private Sub AppendTextRichTextBox1(ByVal message As String) RichTextBox1.AppendText(message) RichTextBox1.SelectionStart = RichTextBox1.TextLength End Sub '別スレッドからボタンの許可設定処理するためデリゲートを利用 Delegate Sub SetButton1EnabledDelegate(ByVal bln As Boolean) Private SetButton1Delegate As New SetButton1EnabledDelegate(AddressOf _ SetButton1Enabled) '[Buttin1]のEnabled設定 Private Sub SetButton1Enabled(ByVal blnEnabled As Boolean) Me.Button1.Enabled = blnEnabled End Sub 'サーバスレッドの数 Private Const numThreads As Integer = 3 'サーバスレッド配列 Private arrServer(numThreads - 1) As Thread 'サーバスレッド終了フラグ件数 Private intServerFinish As Integer = 0 '[Start NamePipe]ボタンクリックイベント Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button1.Click intServerFinish = 0 '名前付きパイプによる通信スレッド開始 For i = 0 To UBound(arrServer) arrServer(i) = New Thread(AddressOf ServerThread) arrServer(i).Start() Next AppendTextRichTextBox1("名前付きパイプ通信待機中・・・" & vbNewLine) '[Start NamePipe]ボタン不可設定 Me.Button1.Enabled = False End Sub '名前付きパイプによる通信スレッド Private Sub ServerThread() 'サーバー用のパイプを双方向でクライアント数は指定値で生成 Dim pipeServer As New NamedPipeServerStream("PIPE_TEST", PipeDirection.InOut, numThreads) Try 'パイプスレッド取得 Dim threadId As Integer = Thread.CurrentThread.ManagedThreadId Me.Invoke(RichTextBox1Delegate, _ New Object() {"パイプ生成完了(スレッドID:" & threadId & ")" & vbNewLine}) 'パイプの接続を待つ pipeServer.WaitForConnection() Me.Invoke(RichTextBox1Delegate, _ New Object() {"パイプ接続完了(スレッドID:" & threadId & ")" & vbNewLine}) 'ストリーム読込を生成 Dim pipeStreamReader As New StreamReader(pipeServer) 'ストリーム書込を生成 Dim pipeStreamWriter As New StreamWriter(pipeServer) pipeStreamWriter.AutoFlush = True Try While True 'パイプからの文字列受信(入力) Dim strRead As String = pipeStreamReader.ReadLine() 'ストリームの末尾に到達した場合はNullなので、通信を終了する If strRead = "" Then Exit While End If '受信文字列の表示 Me.Invoke(RichTextBox1Delegate, _ New Object() {"受信文字列(スレッドID:" & threadId & "):" & strRead & vbNewLine}) '受信文字列に「...OK」を追加して返す Dim strTX As String = strRead & "...OK" pipeStreamWriter.WriteLine(strTX) Me.Invoke(RichTextBox1Delegate, _ New Object() {"受信文字列(スレッドID:" & threadId & "):" & strTX & vbNewLine}) End While Catch ex As IOException MsgBox(ex.Message) Finally pipeStreamReader.Close() '基になるストリームも閉じられるので 'pipeStreamWriter.Close() 'この close はエラーになるので実行しない!! End Try Me.Invoke(RichTextBox1Delegate, New Object() {"パイプ通信の終了" & vbNewLine}) intServerFinish += 1 If intServerFinish >= numThreads Then '[Button1]の許可設定 Me.Invoke(SetButton1Delegate, New Object() {True}) End If Catch ex As Exception MsgBox(ex.Message) Finally pipeServer.Close() End Try End Sub End Class
クライアント側の変更個所は以下の通りです。- パイプ用ストリーム読込オブジェクトを静的変数で宣言し、[Start Named Pipe]ボタンクリック時イベントで生成します。
- [Send Message]ボタンクリック時イベントでサーバーへ文字列を送信後、空文字でなければサーバーからの応答を受信し表示します。 尚、送信文字列が空文字の場合は、パイプ用ストリーム読込オブジェクトをCloseします。
- フォーム上に受信文字列を表示するTEXTBOXを追加します。
クライアント側プログラム
Imports System.IO.Pipes Imports System.IO Imports System.Security.Principal Public Class frmNameClient '名前付きパイプ・クライアント Private pipeStream As NamedPipeClientStream = Nothing 'パイプ用ストリーム書込 Private pipeSw As StreamWriter = Nothing 'パイプ用ストリーム読込 Private pipeSr As StreamReader = Nothing 'フォームロード時イベント Private Sub frmNameClient_Load(sender As Object, e As EventArgs) Handles Me.Load '[Start Named Pipe]ボタンを許可設定 Me.Button1.Enabled = True '[Send Message]ボタンを不可設定 Me.Button2.Enabled = False End Sub '[Start Named Pipe]ボタンクリック時イベント Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try '名前付きパイプ・クライアント生成 pipeStream = New NamedPipeClientStream(".", "PIPE_TEST", _ PipeDirection.InOut, PipeOptions.None, _ TokenImpersonationLevel.Impersonation) '名前付きパイプ接続 pipeStream.Connect(1000) 'ストリーム書込生成 pipeSw = New StreamWriter(pipeStream) pipeSw.AutoFlush = True 'ストリーム読込生成 pipeSr = New StreamReader(pipeStream) '[Start Named Pipe]ボタンを不可設定 Me.Button1.Enabled = False '[Send Message]ボタンを許可設定 Me.Button2.Enabled = True Catch ex As Exception MsgBox(ex.Message) End Try End Sub '[Send Message]ボタンクリック時イベント Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Try '送信文字列取得 Dim strTX As String = Me.TextBox1.Text.Trim Me.TextBox1.Text = "" '送信 pipeSw.WriteLine(strTX) If strTX = "" Then 'ストリーム書込を閉じる pipeSw.Close() 'ストリーム読込を閉じる pipeSr.Close() '名前付きパイプを閉じる pipeStream.Close() '[Start Named Pipe]ボタンを許可設定 Me.Button1.Enabled = True '[Send Message]ボタンを不可設定 Me.Button2.Enabled = False Else '送信後、サーバーからの応答を受信 Dim strRead As String = pipeSr.ReadLine '受信文字列を表示 Me.TextBox2.Text = strRead End If Catch ex As Exception MsgBox(ex.Message) End Try End Sub 'フォームクローズ時イベント Private Sub frmNameClient_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'ストリーム書込を閉じる If Not pipeSw Is Nothing Then pipeSw.Close() End If 'ストリーム読込を閉じる If Not pipeSr Is Nothing Then pipeSr.Close() End If '名前付きパイプを閉じる If Not pipeStream Is Nothing Then pipeStream.Close() End If End Sub End Class
サーバー側のEXEを実行した後、クライアントEXEを3個実行します。 その後、サーバー側の「Start NamePipe」ボタンを押下して、各クライアントの「Start Name Pipe」ボタンを順次押下します。 各クライアント側で送信文字列を入力して「Send Message」ボタンを押下した結果の画像です。 送信した文字列に「...OK」の文字列が追加されて送信されてくる様子が分かります。
尚、以下は各クライアント側で送信文字列をクリアして「Send Message」ボタンを押下して一連の処理を終了した結果の画像です。関連する記事
⇒名前付きパイプを使ったプロセス間通信について
⇒名前付きパイプを使ったプロセス間通信についてその2(複数クライアントとの通信)
⇒Remoting の IPC を使ったプロセス間通信について
⇒Remoting の IPC を使ったプロセス間通信についてその2(HTTPチャネル)
-
名前付きパイプを使ったクライアントプログラムとの通信の説明は以下の記事で行いましたが、 今回はこれを複数のクライアントとの通信が出来る様に変更してみました。
⇒名前付きパイプを使ったプロセス間通信について
変更個所は、通信開始の時にサーバー処理のスレッドを複数立ち上げて、別々のクライアントからの受信をそれぞれのスレッドで受ける様にします。 サーバースレッドの終わりでは、スレッドの終了をスレッドの個数分終わった時点で、再度の通信開始のボタンを押下出来る様にしています。
今回のサーバースレッドの起動数は3個に抑えてありますし、スレッドの終了判定などが適当ですので改良の余地はありますが。サーバー側プログラム
Imports System.Threading Imports System.IO Imports System.IO.Pipes Public Class frmNameServer '別スレッドからメッセージを処理するためデリゲートを利用 Delegate Sub SetRichTextBox1Delegate(ByVal Value As String) Private RichTextBox1Delegate As New SetRichTextBox1Delegate(AddressOf _ AppendTextRichTextBox1) 'リッチテキストボックスにメッセージを表示する Private Sub AppendTextRichTextBox1(ByVal message As String) RichTextBox1.AppendText(message) RichTextBox1.SelectionStart = RichTextBox1.TextLength End Sub '別スレッドからボタンの許可設定処理するためデリゲートを利用 Delegate Sub SetButton1EnabledDelegate(ByVal bln As Boolean) Private SetButton1Delegate As New SetButton1EnabledDelegate(AddressOf _ SetButton1Enabled) '[Buttin1]のEnabled設定 Private Sub SetButton1Enabled(ByVal blnEnabled As Boolean) Me.Button1.Enabled = blnEnabled End Sub 'サーバスレッドの数 Private Const numThreads As Integer = 3 'サーバスレッド配列 Private arrServer(numThreads - 1) As Thread 'サーバスレッド終了フラグ件数 Private intServerFinish As Integer = 0 '[Start NamePipe]ボタンクリックイベント Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button1.Click intServerFinish = 0 '名前付きパイプによる通信スレッド開始 For i = 0 To UBound(arrServer) arrServer(i) = New Thread(AddressOf ServerThread) arrServer(i).Start() Next AppendTextRichTextBox1("名前付きパイプ通信待機中・・・" & vbNewLine) '[Start NamePipe]ボタン不可設定 Me.Button1.Enabled = False End Sub '名前付きパイプによる通信スレッド Private Sub ServerThread() 'サーバー用のパイプを双方向でクライアント数は指定値で生成 Dim pipeServer As New NamedPipeServerStream("PIPE_TEST", PipeDirection.InOut, numThreads) Try 'パイプスレッド取得 Dim threadId As Integer = Thread.CurrentThread.ManagedThreadId Me.Invoke(RichTextBox1Delegate, _ New Object() {"パイプ生成完了(スレッドID:" & threadId & ")" & vbNewLine}) 'パイプの接続を待つ pipeServer.WaitForConnection() Me.Invoke(RichTextBox1Delegate, New Object() {"パイプ接続完了(スレッドID:" & threadId & ")" & vbNewLine}) 'ストリーム読込を生成 Dim pipeStreamReader As New StreamReader(pipeServer) Try While True 'パイプからの文字列受信(入力) Dim strRead As String = pipeStreamReader.ReadLine() 'ストリームの末尾に到達した場合はNullなので、通信を終了する 'If strRead Is Nothing Then If strRead = "" Then Exit While End If '受信文字列の表示 Me.Invoke(RichTextBox1Delegate, New Object() {"受信文字列(スレッドID:" & threadId & "):" & strRead & vbNewLine}) End While Catch ex As IOException MsgBox(ex.Message) Finally pipeStreamReader.Close() End Try 'invokeTextBox(dlg, "パイプ通信を終了します。") Me.Invoke(RichTextBox1Delegate, New Object() {"パイプ通信の終了" & vbNewLine}) intServerFinish += 1 If intServerFinish >= numThreads Then '[Button1]の許可設定 Me.Invoke(SetButton1Delegate, New Object() {True}) End If Catch ex As Exception MsgBox(ex.Message) Finally pipeServer.Close() End Try End Sub End Class
クライアント側プログラムは「名前付きパイプを使ったプロセス間通信について」のものと変わっていません。クライアント側プログラム
Imports System.IO Imports System.IO.Pipes Imports System.Security.Principal Public Class frmNameClient '[Start Named Pipe]ボタンクリック時イベント Private Sub ButtonSend_Click(sender As Object, e As EventArgs) _ Handles ButtonSend.Click Dim pipeStream As NamedPipeClientStream = Nothing Dim pipeSw As StreamWriter = Nothing Try '[Send Message]ボタンを不可設定 Me.ButtonSend.Enabled = False '名前付きパイプ・クライアント生成 pipeStream = New NamedPipeClientStream(".", "PIPE_TEST", _ PipeDirection.InOut, _ PipeOptions.None, _ TokenImpersonationLevel.Impersonation) '名前付きパイプ接続 pipeStream.Connect(1000) 'ストリームライタ pipeSw = New StreamWriter(pipeStream) pipeSw.AutoFlush = True '文字列送信 pipeSw.WriteLine(Me.TextBox1.Text.Trim) Catch ex As Exception MsgBox(ex.Message) Finally 'ストリームライタを閉じる If Not pipeSw Is Nothing Then pipeSw.Close() End If '名前付きパイプを閉じる If Not pipeStream Is Nothing Then pipeStream.Close() End If '[Send Message]ボタンを許可設定 Me.ButtonSend.Enabled = True End Try End Sub End Class
サーバー側のEXEを実行した後、クライアントEXEを3個実行します。 その後、サーバー側の「Start NamePipe」ボタンを押下して、各クライアントの「Start Name Pipe」ボタンを順次押下します。 各クライアント側で送信文字列を入力して「Send Message」ボタンを押下した結果の画像です。
尚、以下は各クライアント側で送信文字列をクリアして「Send Message」ボタンを押下して一連の処理を終了した結果の画像です。関連する記事
⇒Remoting の IPC を使ったプロセス間通信について
⇒Remoting の IPC を使ったプロセス間通信についてその2(HTTPチャネル)
⇒名前付きパイプを使ったプロセス間通信について
⇒名前付きパイプを使ったプロセス間通信についてその3(クライアントとの双方向通信)