我在VS 2010中有一个解决方案:一个WCF服务库项目(“NotifyService”)和两个Windows窗体项目,一个用于服务器(“NotifyServer”),另一个用于客户端(“NotifyClient”)。我的目标是拥有一个双工WCF服务,当服务器推出通知时,它将通知任意数量的连接客户端。除了订阅和取消订阅服务器的更新之外,客户端不需要与服务器通信。但是,我似乎遇到了解决InstanceContext的问题。
以下是WCF服务的代码:
<ServiceContract(
CallbackContract:=GetType(INotifyCallback),
SessionMode:=SessionMode.Required)>
Public Interface INotifyService
<OperationContract()>
Sub Notify(ByVal what As String)
<OperationContract()>
Sub Subscribe()
<OperationContract()>
Sub Unsubscribe()
End Interface
Public Interface INotifyCallback
<OperationContract(IsOneWay:=True)>
Sub OnNotify(ByVal what As String)
End Interface
<ServiceBehavior(
ConcurrencyMode:=ConcurrencyMode.Single,
InstanceContextMode:=InstanceContextMode.PerCall)>
Public Class NotifyService
Implements INotifyService
Private _callbacks As New List(Of INotifyCallback)
Public Sub Notify(ByVal what As String) Implements INotifyService.Notify
For Each callback As INotifyCallback In _callbacks
callback.OnNotify(what)
Next
End Sub
Public Sub Subscribe() Implements INotifyService.Subscribe
Dim client As INotifyCallback = OperationContext.Current.GetCallbackChannel(Of INotifyCallback)()
If Not _callbacks.Contains(client) Then
_callbacks.Add(client)
End If
End Sub
Public Sub Unsubscribe() Implements INotifyService.Unsubscribe
Dim client As INotifyCallback = OperationContext.Current.GetCallbackChannel(Of INotifyCallback)()
If _callbacks.Contains(client) Then
_callbacks.Remove(client)
End If
End Sub
End Class
Server表单引用了服务库创建的DLL,并在代码中自托管WCF服务器的实例:
Public Class frmServer
Private _host As ServiceHost
Private _notifier As NotifyService.NotifyService
Public Sub go() Handles Me.Load
_host = New ServiceHost(GetType(NotifyService.NotifyService), New Uri("net.tcp://localhost:10000"))
_host.AddServiceEndpoint(GetType(NotifyService.INotifyService), New NetTcpBinding, "NotifyService")
_host.Description.Behaviors.Add(New ServiceMetadataBehavior)
_host.AddServiceEndpoint(GetType(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding, "mex")
_host.Open()
_notifier = New NotifyService.NotifyService
End Sub
Private Sub send() Handles Button1.Click
_notifier.Notify("Foo")
End Sub
End Class
在大多数情况下,一切似乎都有效。我可以使用WcfTestClient进行连接,它至少可以看到服务,但由于它是启用了双工的net.tcp绑定,我实际上无法使用该客户端对其进行测试。
当我创建客户端时,我添加了一个名为NotifyGateway的服务引用。这是客户端表单代码:
<CallbackBehavior(
ConcurrencyMode:=ConcurrencyMode.Single,
UseSynchronizationContext:=False)>
Public Class frmClient
Implements NotifyGateway.INotifyServiceCallback
Private _service As NotifyGateway.NotifyServiceClient = Nothing
Public Sub OnNotify(ByVal what As String) Implements NotifyGateway.INotifyServiceCallback.OnNotify
MsgBox(what)
End Sub
Public Sub go() Handles Button1.Click
_service = New NotifyGateway.NotifyServiceClient(New InstanceContext(Me), New NetTcpBinding, New EndpointAddress("net.tcp://localhost:10000/NotifyService"))
_service.Open()
_service.Subscribe()
End Sub
End Class
由于我通过代码设置所有内容,因此没有app.config文件。我遇到的问题是我的Subscribe方法从未被调用过。在调试时,我在单步执行“无法自动进入服务器的行”(不是例外)后收到错误。调试器无法在服务器进程中停止。当我返回到我的服务器表单并单击旨在在回调客户端上引发事件的按钮时,它会进入NotifyService类,但_callbacks
列表为空,这意味着Subscribe方法从未运行过,或者从不运行跑了那个实例。
我已经与此斗争了近一个星期。这至少在服务方面与here所描述的几乎是完全相同的副本,我编译了它并且它有效。所以我有点迷失在哪里我出错......
答案 0 :(得分:0)
我认为问题在于您的服务InstanceContextMode:=InstanceContextMode.PerCall
。这意味着每次调用方法时都会创建一个新的服务实例。因此,当您调用_notifier.Notify("Foo")
时,它会创建服务类的新实例,这意味着它没有任何订阅者。有几种方法可以解决这个问题:
InstanceContextMode:=InstanceContextMode.Single
,这意味着您的服务类将有一个实例来处理服务生命周期的所有请求。与您的其他ConcurrencyMode:=ConcurrencyMode.Single
设置一起,如果您希望大量使用您的服务,这可能会成为瓶颈,因为一次只能处理一个请求。您可以将其更改为ConcurrencyMode:=ConcurrencyMode.Multiple
,但是您必须负责处理类中的线程安全,因为这样可以让您的类的单个实例同时处理多个请求。_callbacks
集合更改为静态(在VB.NET中为Shared)成员。这样,您的服务类的所有实例中的订阅者列表都是相同的。在这种方法中,您将再次负责处理线程安全问题,因为您的服务类的多个实例可能会同时从不同的线程访问单个集合。