2022年4月アーカイブ

前回の非同期処理では、別スレッドからUIコントロールの Label1.Text 更新が出来ませんでしたので、別スレッドからUIコントロール更新の仕方を浚ってみる事に。

別スレッドで実行しているメソッドがUIを書き換える場合、処理をUIスレッドに移行する必要が有りますが、それにはWindowsフォームが備えているInvokeメソッドを呼び出すと良い様です。

尚、別スレッドで実行されると分かっているなら、直接Invokeメソッドを呼び出した方が簡単なのですが、後の改修などを考慮してInvokeRequiredプロパティを使用するのが定石?なんだそうです。


<注意点>

・InvokeRequiredプロパティは、UIスレッドと異なるスレッドで実行していると"真"を返します。

・分かり易い?様に敢えて、別スレッドからUIを更新する為のデリゲートを定義していますが、(関数呼び出しとは違い)デリゲートの名称は区別されず、同一のシグネチャが重要なので、「Invoke(New dlgtShowMsg(AddressOf SetLabel1Text), ・・・)」でも構いません。


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


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

・メッセージボックスの表示は、前回と同じなので省略

・ボタン3を押すと、約2秒後にラベルのテキストを表示

ラベルの更新テキストを表示

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


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

Public Class Form3

    Delegate Sub dlgtShowMsg(ByVal msg As String)

    Private Sub ShowMessage(ByVal msg As String) ' BeginInvokeから呼ばれるので別スレッド
        System.Threading.Thread.Sleep(2000)
        Invoke(New dlgtSetLabel1(AddressOf SetLabel1Text), "Asynchronous Method" & " and " & msg) ' 別スレッドからUIを更新するInvoke呼出し
        MsgBox(msg, MsgBoxStyle.Information, "Asynchronous Method")
    End Sub

    Private Sub showCallback(ByVal ar As IAsyncResult) ' コールバックメソッド
        Dim arShowMsg As dlgtShowMsg

        arShowMsg = CType(ar.AsyncState, dlgtShowMsg)
        arShowMsg.EndInvoke(ar)
    End Sub

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        Dim showMsg As dlgtShowMsg
        Dim ar As IAsyncResult

        showMsg = New dlgtShowMsg(AddressOf ShowMessage)
        ar = showMsg.BeginInvoke("See actions", New AsyncCallback(AddressOf showCallback), showMsg)

        MsgBox("See actions", MsgBoxStyle.Information, "Asynchronous Test")
    End Sub

    Delegate Sub dlgtSetLabel1(ByVal msg As String) ' ①別スレッドからUIを更新するデリゲートの定義

    Private Sub SetLabel1Text(ByVal msg As String) ' ②別スレッドからUIを更新するメソッドの作成
        If InvokeRequired = True Then ' ③Windowsフォームは別スレッドからのUI操作は保障されていない
            Invoke(New dlgtSetLabel1(AddressOf SetLabel1Text), "Required " & msg)
        Else
            Label1.Text = msg
        End If
    End Sub

End Class

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

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

前回、デリゲートの機能の一つを浚いましたので、今回はDelegateの二つ目の機能である「非同期処理」について浚ってみる事に。

色々と調べてみると、VBコンパイラはDelegateを宣言すると、自動的にBeginInvokeメソッドとEndInvokeメソッドを生成してくれます。

UIがフリーズしないようにする為の非同期処理は、この二つのメソッドにより可能となる様です。

【非同期呼出しの勘所】

・呼び出し方:BeginInvoke(デリゲートの仮引数 *1、コールバックデリゲート、Object) As IAsyncResult

・BeginInvoke メソッドを使用する事により、デリゲート経由で呼び出すメソッドを別のスレッドで非同期に実行。

・コールバックメソッドの中で EndInvoke メソッドを呼び出し、メモリリークの可能性を回避。

・デリゲート経由でメソッドを呼び出すと、CLRが管理しているスレッドプールに実行を依頼。その後、制御が直ぐに戻ってくるので呼び出したメソッドとは関係無く、(終了を待たずに)プログラムを先に進める事が可能。

・コールバックメソッドはスレッドプール側で実行されるので、UIを直接更新する事は不可。

*1:引数の数はデリゲート宣言の仮引数に基づくので、お試しソースでは一つ。


<注意点>

・メソッドのシグネチャは同一である事が重要。コールバックメソッドも対応するデリゲートの引数の数と型、戻り値の型に合わせる事。

・BeginInvoke のデリゲート呼出しは非同期で行われる(別スレッド)ので、メソッド呼出し後、直ぐに呼び出し元スレッドに制御が戻る。今回の場合、メッセージボックスのタイトルは「Asynchronous Test」「Asynchronous Method」の順で表示される。

・Windowsフォームは、別スレッドからのUI操作は保障されていないので、②の Label1.Text のコメントを外して実行すると、「有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'Label1' がアクセスされました。」と例外が発生する。


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


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

・ボタン2を押すと、直ぐに表示

Asynchronous Test のタイトルで表示

・その約2秒後に表示

Asynchronous Method のタイトルで表示

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


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

Public Class Form3

    Delegate Sub dlgtDispRpt(ByVal msg As String) ' ①非同期 デリゲートの定義

    Private Sub DisplayRpt(ByVal msg As String) ' ②非同期 デリゲートで呼び出すメソッドの作成
        System.Threading.Thread.Sleep(2000)
        '        Label1.Text = "Asynchronous Method" & " and " & msg
        MsgBox(msg, MsgBoxStyle.Information, "Asynchronous Method")
    End Sub

    Private Sub dispCallback(ByVal ar As IAsyncResult) ' コールバックメソッド
        Dim arDispRpt As dlgtDispRpt

        arDispRpt = CType(ar.AsyncState, dlgtDispRpt)
        arDispRpt.EndInvoke(ar)
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim dispRpt As dlgtDispRpt ' ③非同期 デリゲート型の変数を宣言
        Dim ar As IAsyncResult

        dispRpt = New dlgtDispRpt(AddressOf DisplayRpt) ' ④非同期 デリゲート型変数にメソッドを登録
        ar = dispRpt.BeginInvoke("See actions", New AsyncCallback(AddressOf dispCallback), dispRpt) ' ⑤非同期 デリゲートの呼び出し

        MsgBox("See actions", MsgBoxStyle.Information, "Asynchronous Test")
    End Sub

End Class

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

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

UIは元よりUXは尚更、快適な操作環境を云々、と叫ばれてから早十余年。タッチ操作が主流?となった今では、当然の如き言われ様。

でもね、フリーズしてる訳でもないんだし、プログレスバーでも出しておけば良いんじゃないの?と思ってしまうのはさて置き。

色々検索してみると、即応性=非同期 ⇒ コールバック ⇔ デリゲート が良いらしい(単なるマイクロソフトの推奨?)って事で、

以前、C言語でデリゲートとイベントの概要記事「VC++フォームだからこそ、コマンドプロンプトでEXE?」を書きましたので、それを眺めつつ先ずは、Delegateの使い方を浚ってみる事に。

マイクロソフトのドキュメントによると、

デリゲートは、メソッドを参照するオブジェクトです。デリゲートは他のプログラミング言語で使用される関数ポインターに似ているため、"タイプセーフ関数ポインター"と説明されることがあります。しかしながら、関数ポインターとは異なり、Visual Basicのデリゲートは、System.Delegateクラスに基づく参照型です。

と、何とも要領の得ない、教えたくない様な説明。きっと独自では使って欲しくないのでしょうね。

マイクロソフトのドキュメントによれば、Delegateの構文は、

[<attrlist>][accessmodifier][Shadows] Delegate [Sub | Function] name [(Of typeparamlist)][([parameterlist])][As type]

なので、敢えて大雑把に言ってしまえば以下の様な形式かと。

Delegate Sub デリゲート名称(仮引数リスト...)

または

Delegate Function デリゲート名称(仮引数リスト...) As 戻り値の型

★さて、デリゲートとは?と問われれば一種の型です。利用するためのポイントは以下の5つで、

①デリゲートの宣言(メソッドのシグネチャを定義)
②デリゲートで呼び出すメソッドの作成
③デリゲート型の変数を宣言
④デリゲート型変数にメソッドを登録
⑤デリゲートの呼出し

となるようです。


<注意点>

・デリゲートの機能の一つは、メソッドのアドレスを格納する変数の宣言と呼び出すメソッドを登録出来る型。

・メソッドのシグネチャは同一である事が重要。引数の数と型、戻り値の型が同じ。

・通常のデリゲートの呼出しは同期して行われる(同一スレッド)ので、メソッドの処理が完了してから呼び出し元スレッドに制御が戻る。今回の場合、メッセージボックスのタイトルは「Delegate Method」「Delegate Test」の順で表示される。


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


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

・ボタン1を押すと、約2秒後に表示

Delegate Method のタイトルで表示

・上記画面のOKボタンを押すと、直ぐに表示

Delegate Test のタイトルで表示

・ボタン1を押すと、約2秒後にラベルのテキストを表示

ラベルの更新テキストを表示

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


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

Public Class Form3

    Delegate Sub dlgtDispMsg(ByVal msg As String) ' ①デリゲートの定義

    Private Sub DisplayMsg(ByVal msg As String) ' ②デリゲートで呼び出すメソッドの作成
        System.Threading.Thread.Sleep(2000)
        Label1.Text = "Delegate Method" & " and " & msg
        MsgBox(msg, MsgBoxStyle.Information, "Delegate Method")
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim dispMsg As dlgtDispMsg ' ③デリゲート型の変数を宣言

        dispMsg = New dlgtDispMsg(AddressOf DisplayMsg) ' ④デリゲート型変数にメソッドを登録
        dispMsg("See actions") ' ⑤デリゲートの呼び出し

        MsgBox("See actions", MsgBoxStyle.Information, "Delegate Test")
    End Sub

End Class

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

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

WindowsXPやWindows7とVisualBasic2008でガサゴソやっていると、ますます周回遅れが嵩んで追い付けなくなると言うか、浦島次郎にさえ成れない? という事で、非同期ソケット通信を試してみる事に。

凡そ10年前、Windows 8/RT 以降の Windows Store App では、時間のかかるAPIには非同期バージョンの物しか用意されていない。と言われていましたが、最新のWindowsでは如何なんでしょうか?

同期バージョンのAPIが多数復活(?)してたら、喜びも一塩なんですけれどもね。


<注意点>

・マイクロソフトが言うには、

非同期ソケットは、システムスレッドプールの複数のスレッドを使用して、データの送受信接続を処理します。1つ目はネットワーク接続を処理するスレッド、2つ目はデータの送信または受信の開始を処理するスレッド、他のスレッドはデータの送信または受信を処理するスレッドなどがあります。

らしいので、非同期処理のポイントは「コールバック・デリゲート」で、元々、マルチスレッド処理の事だったのかも?

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


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


/*---- サーバー側 -------------------- お試し結果 --------------------------*/

Waiting for a connection...
Waiting for a connection...
Read 48 bytes from socket.
Data : This is a test.
Asynchronous communications<EOF>
Sent 48 bytes to client.

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

/*---- クライアント側 ---------------- お試し結果 --------------------------*/

Socket connected to ::1:22000
Sent 15 bytes to server.
Sent 33 bytes to server.
Response received : This is a test.
Asynchronous communications<EOF>

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


/*---- サーバー側 ------------------  お試しソース -------------------------*/

'Imports System.Net
'Imports System.Net.Sockets
'Imports System.Text

Public Class Form1

    ' 非同期サーバー受信用状態オブジェクト
    Public Class srvStateObj
        ' データ送受信ソケット
        Public workSocket As Net.Sockets.Socket = Nothing
        ' バッファサイズ
        Public Const BufferSize As Integer = 1024
        ' 受信バッファ
        Public buffer(BufferSize) As Byte
        ' 受信データ文字列
        Public sb As New System.Text.StringBuilder
    End Class 'srvStateObj

    ' イベント待ちハンドラ シグナル設定
    Private acceptDone As New Threading.ManualResetEvent(False)

    Private Sub srvSendCallback(ByVal ar As IAsyncResult)

        Dim cpWkDatSkt As Net.Sockets.Socket ' データ送受信ソケットのコピー
        Dim sndLen As Integer

        ' データ送受信ソケットの取得
        cpWkDatSkt = CType(ar.AsyncState, Net.Sockets.Socket)

        ' データ送信終了
        sndLen = cpWkDatSkt.EndSend(ar)
        Debug.Print("Sent {0} bytes to client.", sndLen)

        cpWkDatSkt.Shutdown(Net.Sockets.SocketShutdown.Both)
        cpWkDatSkt.Close()
    End Sub ' srvSendCallback

    Private Sub srvSend(ByVal wkDatSkt As Net.Sockets.Socket, ByVal data As String)

        Dim byteData(1024) As Byte

        ' 文字列データをバイト列データに変換
        byteData = System.Text.Encoding.ASCII.GetBytes(data)

        ' データ送信開始
        wkDatSkt.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf srvSendCallback), wkDatSkt)
    End Sub ' srvSend

    Private Sub ReadCallback(ByVal ar As IAsyncResult)

        Dim state As srvStateObj
        Dim wkDatSkt As Net.Sockets.Socket ' データ送受信ソケットのコピー

        Dim rdLen As Integer
        Dim msg As String

        ' データ送受信ソケットの取得
        state = CType(ar.AsyncState, srvStateObj)
        wkDatSkt = state.workSocket

        ' データ受信終了
        rdLen = wkDatSkt.EndReceive(ar)

        If rdLen > 0 Then
            ' ストリングビルダーに受信データを格納
            state.sb.Append(System.Text.Encoding.ASCII.GetString(state.buffer, 0, rdLen))

            ' データ受信終了をチェック
            msg = state.sb.ToString()
            If msg.IndexOf("<EOF>") > -1 Then
                ' 全データ受信終了
                Debug.Print("Read {0} bytes from socket. " + vbLf + "Data : {1}", msg.Length, msg)
                ' クライアントへデータ送信(エコーバック)
                srvSend(wkDatSkt, msg)
            Else
                ' 未受信データを受信
                wkDatSkt.BeginReceive(state.buffer, 0, srvStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
            End If
        End If
    End Sub ' ReadCallback

    Private Sub AcceptCallback(ByVal ar As IAsyncResult)

        Dim cpSrvSkt As Net.Sockets.Socket ' サーバー側ソケットのコピー
        Dim datSkt As Net.Sockets.Socket ' クライアント側とのデータ送受信ソケット

        Dim state As srvStateObj

        ' サーバーソケットの取得
        cpSrvSkt = CType(ar.AsyncState, Net.Sockets.Socket)
        ' 接続要求許可受付終了
        datSkt = cpSrvSkt.EndAccept(ar)

        ' イベント完了をセット(メイン処理を続行するため)
        acceptDone.Set()

        ' 非同期受信用状態オブジェクト初期化とデータ送受信ソケット格納
        state = New srvStateObj
        state.workSocket = datSkt
        datSkt.BeginReceive(state.buffer, 0, srvStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
    End Sub ' AcceptCallback

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim srvSkt As Net.Sockets.Socket ' サーバー側ソケット
        Dim port As Integer

        Dim ipHostInfo As Net.IPHostEntry
        Dim ipAddress As Net.IPAddress ' ローカルIPアドレス
        Dim localEP As Net.IPEndPoint

        ' ローカルエンドポイント作成
        port = 22000
        ipHostInfo = Net.Dns.GetHostEntry(Net.Dns.GetHostName())
        ipAddress = ipHostInfo.AddressList(0)
        localEP = New Net.IPEndPoint(ipAddress, port)

        ' ソケット作成
        srvSkt = New Net.Sockets.Socket(ipAddress.AddressFamily, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

        Try
            ' ソケットとローカルエンドポイントの関連付けと、接続要求のリッスン
            srvSkt.Bind(localEP)
            srvSkt.Listen(100)

            While True
                ' イベント状態をリセット
                acceptDone.Reset()

                ' 非同期の接続要求受付開始
                Debug.Print("Waiting for a connection...")
                srvSkt.BeginAccept(New AsyncCallback(AddressOf AcceptCallback), srvSkt)

                ' 接続要求受付まで待ち(ループ継続前の処理がここに在っても良いかも?)
                acceptDone.WaitOne()
            End While

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub

End Class

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

/*---- クライアント側 --------------  お試しソース -------------------------*/

'Imports System.Net
'Imports System.Net.Sockets
'Imports System.Text

Public Class Form1

    ' 非同期クライアント受信用状態オブジェクト
    Public Class clntStateObj
        ' クライアント側ソケット
        Public workSocket As Net.Sockets.Socket = Nothing
        ' バッファサイズ
        Public Const BufferSize As Integer = 256
        ' 受信バッファ
        Public buffer(BufferSize) As Byte
        ' 受信データ文字列
        Public sb As New System.Text.StringBuilder
    End Class 'clntStateObj

    ' イベント待ちハンドラ シグナル設定
    Private connectDone As New Threading.ManualResetEvent(False)
    Private sendDone As New Threading.ManualResetEvent(False)
    Private receiveDone As New Threading.ManualResetEvent(False)

    ' サーバーからの応答データ設定
    Private response As String = String.Empty

    Private Sub ReceiveCallback(ByVal ar As IAsyncResult)

        Dim state As clntStateObj
        Dim wkClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー

        Dim rcvLen As Integer

        Try
            ' クライアントソケットを取得
            state = CType(ar.AsyncState, clntStateObj)
            wkClntSkt = state.workSocket

            ' データ受信終了
            rcvLen = wkClntSkt.EndReceive(ar)

            If rcvLen > 0 Then
                ' ストリングビルダーに受信データを格納
                state.sb.Append(System.Text.Encoding.ASCII.GetString(state.buffer, 0, rcvLen))

                ' 残りのデータを取得
                wkClntSkt.BeginReceive(state.buffer, 0, clntStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
            Else
                ' 全データ受信
                If state.sb.Length > 1 Then
                    response = state.sb.ToString()
                End If
                ' 全データ受信完了イベントセット(メイン処理を続行するため)
                receiveDone.Set()
            End If

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' ReceiveCallback

    Private Sub Receive(ByVal clntSkt As Net.Sockets.Socket)

        Dim state As clntStateObj

        ' 非同期受信用のオブジェクトの初期化とクライアントソケット格納
        state = New clntStateObj
        state.workSocket = clntSkt

        ' データ受信開始
        clntSkt.BeginReceive(state.buffer, 0, clntStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
    End Sub ' Receive

    Private Sub clntSendCallback(ByVal ar As IAsyncResult)

        Dim cpClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー
        Dim sndLen As Integer

        Try
            ' クライアントソケットを取得
            cpClntSkt = CType(ar.AsyncState, Net.Sockets.Socket)

            ' 送信完了
            sndLen = cpClntSkt.EndSend(ar)
            Debug.Print("Sent {0} bytes to server.", sndLen)

            ' 送信完了イベントセット(メイン処理を続行するため)
            sendDone.Set()

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' clntSendCallback

    Private Sub clntSend(ByVal clntSkt As Net.Sockets.Socket, ByVal data As String)

        Dim byteData(1024) As Byte

        ' 文字列データをバイト列データに変換
        byteData = System.Text.Encoding.ASCII.GetBytes(data)

        ' データ送信開始
        clntSkt.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf clntSendCallback), clntSkt)
    End Sub ' clntSend

    Private Sub ConnectCallback(ByVal ar As IAsyncResult)

        Dim cpClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー

        Try
            ' クライアントソケットを取得
            cpClntSkt = CType(ar.AsyncState, Net.Sockets.Socket)

            ' 接続完了
            cpClntSkt.EndConnect(ar)
            Debug.Print("Socket connected to {0}", cpClntSkt.RemoteEndPoint.ToString())

            ' 接続完了イベントセット(メイン処理を続行するため)
            connectDone.Set()

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' ConnectCallback

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim clntSkt As Net.Sockets.Socket ' クライアント側ソケット
        Dim port As Integer

        Dim ipHostInfo As Net.IPHostEntry
        Dim ipAddress As Net.IPAddress ' リモートIPアドレス
        Dim remoteEP As Net.IPEndPoint

        ' リモートエンドポイント作成
        port = 22000
        ipHostInfo = Net.Dns.GetHostEntry(Net.Dns.GetHostName())
        ipAddress = ipHostInfo.AddressList(0)
        remoteEP = New Net.IPEndPoint(ipAddress, port)

        ' ソケット作成
        clntSkt = New Net.Sockets.Socket(ipAddress.AddressFamily, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

        ' イベント状態をリセット
        connectDone.Reset()
        sendDone.Reset()
        receiveDone.Reset()

        ' 応答データをクリア
        response = String.Empty

        ' サーバーへ接続要求開始
        clntSkt.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), clntSkt)
        ' 接続完了待ち
        connectDone.WaitOne()

        ' サーバーへデータ送信開始
        clntSend(clntSkt, "This is a test.")
        ' 送信完了待ち
        sendDone.WaitOne()

        ' 送信イベント状態をリセット
        sendDone.Reset() ' 05/29 訂正
        clntSend(clntSkt, vbLf + "Asynchronous communications<EOF>")
        sendDone.WaitOne()

        ' サーバーからの応答受信開始
        Receive(clntSkt)
        ' 受信完了待ち
        receiveDone.WaitOne()

        Debug.Print("Response received : {0}", response)

        ' ソケット解放
        clntSkt.Shutdown(Net.Sockets.SocketShutdown.Both)
        clntSkt.Close()
    End Sub

End Class

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

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

前回のように同期ソケット通信にマルチスレッドを追加してノンブロッキングモードを実現するなら、最初っから非同期ソケット通信にしたらどうだろうと、試してみる事に。

マイクロソフトのドキュメントによると、

・非同期サーバーソケットは、.NET Frameworkの非同期プログラミングモデルを使用してネットワークサービス要求を処理します。Socketクラスは、標準の.NET Frameworkの非同期名前付けパターンに従います。例えば、同期のAcceptメソッドは非同期のBeginAcceptメソッドとEndAcceptメソッドに対応します。

非同期サーバーソケットには、ネットワークからの接続要求の受け入れを開始するメソッド、接続要求を処理してネットワークからデータの受信を開始するコールバックメソッド、データの受信を終了するコールバックメソッドが必要です。

・非同期クライアントソケットは、標準の.NET Framework非同期プログラミングモデルを使用して、1つのスレッドでネットワーク接続を処理しながら、アプリケーションは元のスレッドで実行を継続します。非同期メソッドの場合、Socketクラスは.NET Frameworkの名前付けパターンに従います。例えば、同期のReceiveメソッドは非同期のBeginReceiveメソッドとEndReceiveメソッドに対応します。

非同期操作には、操作の結果を返すコールバックメソッドが必要です。結果を知る必要がないアプリケーションの場合、コールバックメソッドは必要ありません。

非同期クライアントソケットには、ネットワークデバイスへの接続を開始するメソッドと接続を完了するコールバックメソッド、データの送信を開始するメソッドと送信を完了するコールバックメソッド、およびデータの受信を開始するメソッドとデータの受信を終了するコールバックメソッドが必要です。

・何れのコールバックメソッドもAsyncCallbackデリゲートを実装しており、voidを返してIAsyncResult型の1つのパラメーターを受け取ります。

らしいのですが、今一つピンと来ないのでSocket操作の表を作ってみました。

非同期サーバー/クライアントのソケット操作
非同期サーバーソケット非同期クライアントソケット
操作・動作 メ *1 サ *2 デ *3 操作・動作 メ *1 サ *2 デ *3
Button1_Click     Button1_Click    
BeginAccept が
AcceptCallback 呼出し
  BeginConnect が
ConnectCallback 呼出し
 
AcceptCallback     ConnectCallback    
EndAccept     EndConnect    
BeginReceive が
ReadCallback 呼出し
  Button1_Click    
ReadCallback     Send 呼出し    
EndReceive     Send    
Send 呼出し     BeginSend が
SendCallback 呼出し
 
Send (ReadCallback)     SendCallback    
BeginSend が
SendCallback 呼出し
  EndSend    
SendCallback     Button1_Click    
EndSend     Receive 呼出し    
  Receive    
BeginReceive が
ReceiveCallback 呼出し
 
ReceiveCallback    
EndReceive    
(BeginReceive が
ReceiveCallback 呼出し)
  (↑) (○)

 *1:メインスレッド
 *2:サブスレッド
 *3:AsyncCallbackのデリゲート実装を示す

でも、却って分かり辛くなってしまった感じも?
あと、非同期にすると例外処理がどう変わるのかも、今一不明(ちゃんと調べろよ!)。


<注意点>

・動作は非同期なので、ノンブロッキングモードらしいです。