#vb.net #wcf
#vb.net #wcf
Вопрос:
У меня есть решение в VS 2010: проект библиотеки служб WCF («NotifyService») и два проекта Windows Forms, один для сервера («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
Форма сервера содержит ссылку на 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 нет. Проблема, с которой я сталкиваюсь, заключается в том, что мой метод подписки никогда не вызывается. При отладке я получаю сообщение об ошибке после перехода через эту строку (не исключение), в котором говорится: «Невозможно автоматически перейти на сервер. Отладчику не удалось остановить серверный процесс «. Когда я возвращаюсь к своей форме сервера и нажимаю кнопку, предназначенную для вызова события на клиентах обратного вызова, оно переходит в класс NotifyService, но _callbacks
список пуст, что означает, что метод Subscribe либо никогда не запускался, либо никогда не запускался в этом экземпляре.
Я боролся с этим почти неделю. Это почти идентичная копия, по крайней мере, на стороне службы, тому, что было описано здесь, и я скомпилировал это, и это работает. Итак, я немного запутался в том, где я ошибаюсь…
Ответ №1:
Я думаю, что проблема связана с InstanceContextMode:=InstanceContextMode.PerCall
вашим сервисом. Это означает, что новый экземпляр вашей службы создается каждый раз, когда вызывается метод. Поэтому, когда вы вызываете _notifier.Notify("Foo")
, он создает новый экземпляр вашего класса обслуживания, что означает, что у него нет подписчиков. Есть несколько способов, которыми вы можете подойти к этому:
- Одним из способов было бы установить
InstanceContextMode:=InstanceContextMode.Single
, что означает, что будет один экземпляр вашего класса обслуживания, который будет обрабатывать все запросы в течение всего срока службы вашего сервиса. Вместе с другой вашей настройкойConcurrencyMode:=ConcurrencyMode.Single
это может быть узким местом, если вы ожидаете, что ваша служба будет использоваться интенсивно, поскольку одновременно будет обрабатываться только один запрос. Вы можете изменить его наConcurrencyMode:=ConcurrencyMode.Multiple
, но тогда вам придется отвечать за обработку потокобезопасности внутри вашего класса, поскольку это позволяет одному экземпляру вашего класса обрабатывать несколько запросов одновременно. - Другим подходом было бы изменить вашу
_callbacks
коллекцию на статическую (совместно используемую в VB.NET ) член клуба. Таким образом, ваш список подписчиков будет одинаковым во всех экземплярах вашего класса обслуживания. При таком подходе вы снова будете нести ответственность за решение проблем потокобезопасности, поскольку будет несколько экземпляров вашего класса обслуживания, которые могут одновременно обращаться к одной коллекции из разных потоков. - Вы могли бы использовать некоторый тип механизма сохранения, такой как база данных. Это было бы наиболее сложным, поскольку вам в основном пришлось бы заново создавать новый прокси для каждого подписчика для каждого уведомления, но имеет то преимущество, что вы не потеряете всех своих подписчиков, если ваше приложение будет закрыто. Похоже, это, вероятно, не самое подходящее для вашей ситуации.
Комментарии:
1. Пункт 2 сделал это. Сделано из-за моего собственного отсутствия внимания к преобразованию C # в VB. Это не будет иметь большого смысла — максимум несколько компьютеров, и все в одной сети, так что (не съеживайтесь) Я не слишком беспокоюсь о безопасности потоков. Общие члены будут работать просто отлично. Спасибо!
2. Я бы пошел с
InstanceContextMode.Single
/ConcurrencyMode.Single
then. Это только замедлит работу, если вы получите несколько запросов одновременно, и именно тогда у вас возникнут проблемы с параллелизмом.