簡體   English   中英

如何在沒有崩潰應用程序的情況下通過WCF雙工服務發送消息?

[英]How to send message through WCF duplex service without crash application?

我有一個WCF服務,用於在管理PC多台 客戶端PC之間進行通信。

在這里,WCF服務將實時托管,而Admin PC和Client PC將使用WPF中內置的應用程序。 WCF充當雙工服務,用於處理事件和向其他用戶廣播事件。

就像管理員將消息發送到服務一樣,它將被廣播到所有客戶端,而當客戶端將消息發送到服務時,它將也會廣播到所有其他用戶。

當有多個 消息 發送服務時,將使應用程序崩潰 在這里,我將代碼放在下面,因此請檢查並建議我解決此問題。

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類:

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上應用While循環時,它像崩潰的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()
                       });
}
          }
        }

我檢查了調試器的設置,但不知道是哪部分使我的應用程序崩潰以及為什么它在快速通信中發生。


這是我對問題的評論的摘要。


我認為在您的負載下調用服務時,代碼中會出現一些問題。

您的服務標記為這個WCF一個多線程感知服務ServiceBehavior聲明。

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, // singleton
                 ConcurrencyMode = ConcurrencyMode.Multiple)] // thread-safe(?)
public class BroadcastorService : IBroadcastorService1 { ... }

InstanceContextMode.Single非常簡單,因為WCF無需您真正要做的任何事情為您進行設置, 但是 ConcurrencyMode.Multiple是您可以接受多個線程進行的多個並發調用的聲明 您聲明自己承擔所有責任,並且不對WCF負責。 WCF相信您不會踢腳。

通常,最好讓WCF確定來自客戶端的呼叫如何以及何時進入您的服務代碼。 默認情況下,WCF 串行化所有呼叫同時調用你的服務的一個方法,使您的服務線程安全的 ,而無需做任何可怕的開發商lock()秒。 InstanceContextMode使用時,可以導致服務主機更好的可伸縮性

還要考慮每個WCF服務方法要做的第一件事就是對整個硬編碼單例執行lock() ,而ConcurrencyMode.Multiple並沒有帶來任何好處。 您也可以使用ConcurrencyMode.Single ,刪除所有的lock()然后讓WCF為您完成方法調用的所有序列化。 比手動使服務成為線程安全的安全得多。 另外,如果您想刪除服務的單例性質並使用InstanceContextMode.PerCallInstanceContextMode.PerSession ,這很可能是單行更改。

當您的應用加載時,您的服務被標記為:

  • “我可以處理任意數量的並發線程調用” :P和
  • “讓所有線程針對同一個服務對象執行” :P

...可能會導致非常危險的服務。 通過進行上述建議的更改,您可以有效地限制來自所有客戶端的並發呼叫數,從而使服務更加穩定

崩潰,異常處理和調試提示

您提到您的應用程序崩潰了,但您沒有說明錯誤是什么。 查看您的代碼,我可以看到很多:

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

通常,您要避免做這種事情,因為您是在告訴.NET您要靜默捕獲所有異常。 作為開發人員,您甚至都沒有意識到代碼中可能存在討厭的錯誤。 捕獲所有異常是相當調皮 ,你真的想正好趕上您所期待的那些,或許設置了一切別的地方在你的代碼中未處理的異常處理程序 ,只顯示一個致命的消息給用戶關閉應用程序有點優雅前。

要改善調試,請確保在Visual Studio調試器中運行應用程序。

從“ 調試”菜單中,選擇“ Debug.Windows.Exception設置”

在出現的工具窗口中,選中“ 公共語言運行時例外 ”框。 這告訴VS,您想被告知所有 CLR異常。 (稍后您可以進入,然后選擇您想要的人)

現在,每當引發異常時,調試器就會停止並將光標放在有問題的行上。 注意:這是所謂的“ 第一次機會異常”,因為調試器立即停止在線。 讓我們想象一下拋出了TimeoutException 這不一定是錯誤,因為您可能會說某個地方有一個catch (TimeoutException) 它尚未進行到第一個catch() (如果有的話)塊,因此請勿驚慌。 如果按F5 (或Debug.Continue菜單),則調試器將繼續在catch(TimeoutException)處停止應用程序。 現在,如果您沒有在“調試設置”中打勾,則調試器將直接進入您的catch(TimeoutException)而不會發出優先機會通知。 現在的問題是,如果不查看Exception對象中的調用堆棧,就不知道錯誤發生在哪里。

代理客戶

盡管可能不是緊迫的問題,但我也注意到您的客戶端正在創建WCF代理並將其存儲在應用程序的MainWindow類的字段中。 關於代理的事情是,它們在一段時間后會破裂,WCF也不例外。 通常,它們代表網絡連接。 網絡來來往往。 如果空閑 ,連接可以簡單地超時並被服務器關閉 直到它去調用它之前,客戶端都不會知道它。 您將得到一個xxxException並且該代理將被標記為錯誤 ,這意味着它不能再次使用 您需要再做一個。

因此,通常最好在進行首次調用之前在該位置創建一個代理,然后在完成當前一批調用后將其刪除(您應該使用Dispose() )。 這樣,或者在您的應用程序中內置它可以處理WCF錯誤並在需要時重新創建代理。

現在,根據您使用的WCF綁定,超時可能有所不同,可能是1分鍾,5分鍾或10分鍾。

同樣,這只是一個僅供參考,我不認為這是在這里發生,但您永遠都不會知道。

不要讓UI線程調用WCF服務

OP:

從管理員端開始向服務發送信號時,我無法在管理員屏幕上執行任何操作。 甚至我也無法最小化,最大化或關閉該屏幕

您的Admin客戶端處於凍結狀態,因為您正在從btnSendEvent_Click處理程序調用WCF服務。 在該方法返回之前,UI不會執行任何操作。 這是所有UI的本質。 沒有 UI是多線程的。 您的點擊處理程序正在執行昂貴且及時的網絡呼叫這一事實只會使您的UI明顯變得無響應。 也許您需要在BackgroundWorker組件提供的工作線程(更容易完成更改)中調用它,或者通過async/await (更好)進行async/await調用。

OP:

非常感謝您的支持。 現在,我已在管理端應用程序中使用BackgroundWorker ,並按照您的指示將更改應用於WCF服務。 現在它正在 平穩地 發送信號。 不會崩潰和凍結管理端應用程序。

我很高興聽到我的建議幫助解決了這個問題。

告訴我更多

我強烈建議以書籍/ Kindle形式使用這本出色的WCF聖經:

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM