WCFで双方向、つまりサービスからクライアントへのコールバックをおこなう際の注意点。
【WCF仕様】
PerSession, Duplex, NetTCPBinding, ConcurrencyMode=Single
【コード】
※コントラクトや構成ファイル、SVCUTIL.exeによる自動生成クライアントコード等は一切省略。
・Testメソッドを呼び出すとTestEventコールバックを発生させるというもの。
【サービス定義】
' User Service Callback
Public Interface ITestCallback
Sub TestEvent()
End Interface
' User Service
Public Class TestService
' Service Method
Pubic Sub SomeMethod()
End Sub
' Service Method
Pubic Sub Test()
' この実行コンテキストが完了したタイミングでコールバックをおこなう
AddHandler OperationContext.Current.OperationCompleted, AddressOf TestCompleted
End Sub
' Test Method Completed
Pubic Sub TestCompleted(ByVal sender As Object, ByVale As EventArgs)
Dim cb As ITestCallback = OperationContext.Current.GetCallbackChannel(Of ITestCallback)()
cb.TestEvent()
End Sub
End Class
【クライアント アプリケーション】
・サービスメソッドを連続で呼び出す。
Private Sub Button1_Click()
_TestClient.Test()
_TestClient.SomeMethod() *1
End Sub
【現象】
タイムアウトにて例外発生。
現象的にはTest→SomeMethodの時点でSomeMethodの実行コンテキスト中にコールバックを送信しようとするため。
※予想。 というか例外トレースの最終メソッドがSomeMethodで例外後にコールバック受信プロシージャに遷移する。
この挙動はコントラクトのOneWayや同期/非同期呼び出しに関係なく発生する。
元々メッセージが完結(実行コンテキストの完了=呼び出し元のステップを抜ける)を待たずに
別のメッセージを発生させるとブロックされタイムアウトになることはMSDNで確認済み。
なお、*1 をなくすと例外なく動作する。
ちなみに実際の実装例としては、ログイン→ログイン通知受信→メンバ一覧取得のような場合がある。
【現状の回避策】
コールバックのコンテキスト(受信の実装)はクライアントに記述するが、
そのコントラクトのConcurrencyMode=Singleによりコールバック受信時の処理が同期になる。
現象で書いたように、Test→SomeMethodが動作するのはそれぞれがサービスへの要求つまり一方向であり
また連続するステートメント(同期)であるのでTestの実行コンテキストが完了してからSomeMethodを送信しているためである。
このことから、双方向のメッセージが(非同期のため?)重なってしまうことに原因がある。
そこでコールバック専用のサービスを用意する。つまりポートを2つ使うことになる。
さらにOperationCompletedイベントを
サービス側:サービス要求受信→コールバック送信、クライアント側:コールバック受信→サービス要求
の間で利用してメッセージを分断することにより上記動作を実現している。
#MSDNが未整備で例も少なく大変です。。。
#そんなわけで誰に確認したわけでもありません。あくまで私の調べた結果であります。
#したがって、突っ込みは大歓迎です。間違ってたらえらいことになりますので(自分が)