Как отправить сообщение через дуплексный сервис WCF без сбоя приложения? - PullRequest
1 голос
/ 22 июня 2019

У меня есть служба WCF для связи между ПК администратора и несколькими Клиентскими ПК .

Здесь служба WCF будет размещенав прямом эфире, в то время как ПК администратора и клиентский ПК используют приложение, встроенное в WPF.WCF работает как duplex служба для обработки событий и трансляции событий другим пользователям.

Например, если администратор отправит сообщение службе, оно будет передано всем клиентам, тогда как когда клиент отправит сообщение службе, оно также будет отправлено всем другим пользователям.

, когда несколько сообщения являются отправкой в службу , затем сбой приложения .Здесь я поместил свой код ниже, поэтому, пожалуйста, проверьте его и предложите мне решить эту проблему.

Код службы WCF

IBroadcastorService1 Интерфейс:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = 
typeof(IBroadcastorCallBack1))]
public interface IBroadcastorService1
{
    [OperationContract(IsOneWay = true)]
    void RegisterClient(string clientName);

    [OperationContract(IsOneWay = true)]
    void NotifyServer(EventDataType eventData);
}
public interface IBroadcastorCallBack1
{
    [OperationContract(IsOneWay = true)]
    void BroadcastToClient(EventDataType eventData);
}
[DataContract]
public class EventDataType
{
    [DataMember]
    public string ClientName { get; set; }

    [DataMember]
    public string EventMessage { get; set; }
}

BroadcastorService.svc.cs содержит следующий код:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class BroadcastorService : IBroadcastorService1
{
    private static Dictionary<string, IBroadcastorCallBack1> clients = new Dictionary<string, IBroadcastorCallBack1>();
    private static object locker = new object();

    public void RegisterClient(string clientName)
    {
        if (clientName != null && clientName != "")
        {
            try
            {
                IBroadcastorCallBack1 callback = OperationContext.Current.GetCallbackChannel<IBroadcastorCallBack1>();
                lock (locker)
                {
                    //remove the old client
                    if (clients.Keys.Contains(clientName))
                        clients.Remove(clientName);
                    clients.Add(clientName, callback);
                }
            }
            catch (Exception ex)
            {
            }
        }
    }

    public void NotifyServer(EventDataType eventData)
    {
        lock (locker)
        {
            var inactiveClients = new List<string>();
            foreach (var client in clients)
            {
                if (client.Key != eventData.ClientName)
                {
                    try
                    {
                        client.Value.BroadcastToClient(eventData);
                    }
                    catch (Exception ex)
                    {
                        inactiveClients.Add(client.Key);
                    }
                }
            }

            if (inactiveClients.Count > 0)
            {
                foreach (var client in inactiveClients)
                {
                    clients.Remove(client);
                }
            }
        }
    }
}

}

И файл web.config имеет вид:

<services>
  <service behaviorConfiguration="Service" name="WcfMultipleCallBacks.BroadcastorService">
    <endpoint address="" binding="wsDualHttpBinding" contract="WcfMultipleCallBacks.IBroadcastorService1" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>

Код приложения WPF Admin,Я создал класс для обработки событий, относящихся к справке службы WCF: BroadcastorCallback.cs class:

public class BroadcastorCallback : IBroadcastorService1Callback
{
        private System.Threading.SynchronizationContext synContext = AsyncOperationManager.SynchronizationContext;
        private EventHandler _broadcastorCallBackHandler;


        //SetHandler, is used to set the callback handler for the client.
        public void SetHandler(EventHandler handler)
        {
            this._broadcastorCallBackHandler = handler;
        }

//BroadcastToClient, is used to allow the service to call the client. 
        //When other clients send an event notification to the service, the service will connect to this client 
        //through the callback channel, then call this method to notify this client the event.
        public void BroadcastToClient(EventDataType eventData)
        {
            synContext.Post(new System.Threading.SendOrPostCallback(OnBroadcast), eventData);
        }

        //OnBroadcast, is the connection between the client callback handler, which is set in the first method, 
        //and the actual service call, which will be invoked by the service through the second method. 
        //When the service calls the second method, BroadcastToClient, to notify a event, the call will be delegated to 
        //this method, OnBroadcast, and then the same call will be delegated to the client callback handler.
        public void OnBroadcast(object eventData)
        {
            this._broadcastorCallBackHandler.Invoke(eventData, null);
        }

   }
}

В то время как MainWindow.cs содержит такой код:

private ServiceReference1.BroadcastorService1Client _client;
public MainWindow()
        {
            InitializeComponent();
            RegisterClient();
        }
private delegate void HandleBroadcastCallback(object sender, EventArgs e);
        public void HandleBroadcast(object sender, EventArgs e)
        {
            try
            {
                var eventData = (ServiceReference1.EventDataType)sender;
                if (this.txtEventMessages.Text != "")
                    this.txtEventMessages.Text += "\r\n";
                this.txtEventMessages.Text += string.Format("{0} (from {1})",
                    eventData.EventMessage, eventData.ClientName);
            }
            catch (Exception ex)
            {
            }
        }

private void RegisterClient()
        {
            if ((this._client != null))
            {
                this._client.Abort();
                this._client = null;
            }

            BroadcastorCallback cb = new BroadcastorCallback();
            cb.SetHandler(this.HandleBroadcast);

            System.ServiceModel.InstanceContext context = new System.ServiceModel.InstanceContext(cb);
            this._client = new ServiceReference1.BroadcastorService1Client(context);

            //this._client.RegisterClient(this.txtClientName.Text);
            this._client.RegisterClient("Harry Potter");
        }

private void btnSendEvent_Click(object sender, RoutedEventArgs e)
        {
this._client.NotifyServer(
                       new ServiceReference1.EventDataType()
                       {
                           ClientName = "Harry Potter",
                           //EventMessage = this.txtEventMessage.Text
                           EventMessage = count.ToString()
                       });
          }
        }

Этот код работает отличнокогда есть отправка сообщения не слишком быстрая.Но когда обмен сообщениями происходит слишком быстро, происходит сбой приложения wpf.

Для целей тестирования, когда я применяю цикл "Ввоз" в SendEvent, происходит сбой приложения WPF, например.

private bool isRun = false;
private void btnSendEvent_Click(object sender, RoutedEventArgs e)
        {
isRun = true;
while(isRun = true)
{
this._client.NotifyServer(
                       new ServiceReference1.EventDataType()
                       {
                           ClientName = "Harry Potter",
                           //EventMessage = this.txtEventMessage.Text
                           EventMessage = count.ToString()
                       });
}
          }
        }

Я проверяю с помощьюнастройка отладчика, но не знаю, какая часть вызывает сбой моего приложения и почему это происходит при быстром обмене данными.

1 Ответ

0 голосов
/ 22 июня 2019

Это краткое изложение моих комментариев над вопросом.


Я думаю, что в вашем коде есть несколько проблем, которые возникают, когда вы вызываете сервис под нагрузкой.

Ваша декларация WCF ServiceBehavior.

помечена как singleton , многопоточная служба с поддержкой многопоточности. *1012*
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, // singleton
                 ConcurrencyMode = ConcurrencyMode.Multiple)] // thread-safe(?)
public class BroadcastorService : IBroadcastorService1 { ... }

InstanceContextMode.Single прост, так как WCF настраивает это для вас, не имея ничего, что вам действительно нужно сделать однако ConcurrencyMode.Multiple является декларацией с вашей стороны, что вы можете принимать несколько одновременных вызовов несколькими потоками. Вы заявляете, что принимаете на себя всю ответственность и не несете ответственности за WCF. WCF доверяет вам, что вы не будете стрелять себе в ногу.

Обычно лучше, чтобы WCF определял, как и когда звонки клиентов попадают в код вашей службы. По умолчанию WCF сериализует все вызовы, вызывающие методы в вашем сервисе по одному , что делает ваш сервис поточно-ориентированным , при этом разработчикам не нужно ничего делать со страшными lock() s. При использовании с InstanceContextMode это может привести к лучшей масштабируемости вашего сервисного хоста.

Учтите также, что первое, что делает каждый из ваших методов обслуживания WCF, - это выполнение lock() для всего жестко запрограммированного синглтона, вы не получаете никакой выгоды от ConcurrencyMode.Multiple. Вы также можете использовать ConcurrencyMode.Single, удалить все lock() s и позволить WCF выполнить всю сериализацию вызовов методов за вас. Гораздо безопаснее , чем ручная настройка службы. Плюс, если вы когда-нибудь захотите удалить одноэтапную природу вашего сервиса и использовать, скажем, InstanceContextMode.PerCall или InstanceContextMode.PerSession, это, вероятно, изменение в одну строку.

Когда ваше приложение находится под нагрузкой, тот факт, что ваша служба помечена как:

  • «Я могу обрабатывать любое количество одновременных вызовов потоков» : P и
  • «Пусть все потоки выполняются для одного и того же сервисного объекта» : P

... может привести к очень рискованному обслуживанию. Внося предложенные выше изменения, вы эффективно дросселируете количество одновременных вызовов от всех ваших клиентов , таким образом делая услугу более стабильной .

Сбои, Советы по обработке исключений и отладке

Вы упомянули, что ваше приложение падает, но вы не сказали, в чем была ошибка. Глядя на ваш код, я вижу много:

try
{
    // something here
}
catch (Exception ex)
{
}

Как правило, вы хотите избегать подобных действий, потому что вы говорите .NET, что хотите молча перехватить все исключения. Вам, как разработчику, даже не сообщают о возможных неприятных ошибках в вашем коде. Поймать все исключения довольно непослушно , поскольку вы действительно хотите просто поймать те, которые ожидаете, и, возможно, настроить обработчик необработанных исключений для всего остального в вашем коде, который просто отображает фатальное сообщение пользователю перед тем, как закрыть приложение несколько изящно .

Чтобы улучшить отладку, убедитесь, что вы запускаете приложение в отладчике Visual Studio.

В меню Debug выберите Debug.Windows.Exception Settings .

В появившемся окне инструментов установите флажок Исключения общего времени выполнения . Это говорит VS о том, что вы хотите получать информацию о всех CLR исключениях. (позже вы можете войти и просто выбрать те, которые хотите)

Теперь, когда выдается исключение, отладчик останавливается и помещает курсор в строку с ошибкой. Примечание: на данный момент это то, что известно как исключение первого шанса , поскольку отладчик немедленно останавливается на линии.Давайте представим, что TimeoutException был брошен.Это не обязательно ошибка, как вы могли бы сказать, где-то есть catch (TimeoutException).Он еще не перешел к первому catch() (если есть) блоку, так что не пугайтесь.Если вы нажмете F5 (или Debug.Continue menu), то отладчик возобновит остановку приложения на вашем catch(TimeoutException).Если бы вы не поставили галочку в окне «Параметры отладки», отладчик просто перешел бы прямо к вашему catch(TimeoutException), не отправляя уведомление с первым шансом.Проблема в том, что теперь вы не знаете, где произошла ошибка, не глядя на стек вызовов в объекте Exception.

Прокси-клиенты

Хотя, вероятно, это и не непосредственная проблема, я тоже замечаючто ваши клиенты создают прокси WCF и хранят его в поле класса MainWindow вашего приложения.Суть прокси в том, что через некоторое время они могут сломаться, и WCF не является исключением.Обычно они представляют собой сетевые подключения.Сети приходят и уходят.Соединения могут просто тайм-аут , если простаивает и быть закрыт сервером.Клиент не узнает об этом, пока не отправится вызывать его, к тому времени, когда будет слишком поздно.Вы получите xxxException, а прокси-сервер будет помечен как неисправен , что означает, что его нельзя использовать снова .Вам нужно будет сделать еще один.

По этой причине, как правило, лучше создать прокси в точке до того, как вы сделаете свой первый вызов, а затем избавиться от него (вы должны Dispose() это) один раз.текущая партия вызовов завершена.Это или встроить в ваше приложение то, что оно может обрабатывать ошибки WCF и заново создавать прокси-сервер, когда это необходимо.

Теперь, в зависимости от используемой привязки WCF, таймауты могут различаться, это может быть 1, 5или 10 минут.

Опять же, это просто К вашему сведению, я не думаю, что это происходит здесь, но вы никогда не знаете.

Не позволяйте потоку пользовательского интерфейса вызывать службу WCF

OP:

Когда отправка сигнала начинается со стороны администратора на обслуживание, я не могу ничего делать на экране администратора.Даже я не могу свернуть, развернуть или закрыть этот экран

Ваш клиент Admin зависает, потому что вы вызываете службу WCF из обработчика btnSendEvent_Click.Пользовательский интерфейс не будет ничего делать, пока этот метод не вернется.Это природа всего пользовательского интерфейса. Нет Пользовательский интерфейс является многопоточным.Тот факт, что ваш обработчик кликов выполняет дорогие и своевременные сетевые вызовы, просто сделает ваш пользовательский интерфейс более незаметным.Возможно, вам нужно вызвать его внутри рабочего потока, который предоставляет компонент BackgroundWorker (новичок проще) или асинхронно через async/await (лучше).

OP:

Спасибоочень за вашу поддержку.Прямо сейчас я использую BackgroundWorker в приложении на стороне администратора и применяю изменения в службе WCF согласно вашей инструкции.Теперь отправляет сигнал плавно . без сбоев и зависаний приложения на стороне администратора.

Я рад слышать, что мое предложение помогло решить проблему.

Расскажите подробнее

Я очень рекомендую эту превосходную библию WCF в любой книге / форме Kindle:

enter image description here

...