[英]deadlock when using WCF Duplex Polling with Silverlight
我已經按照Tomek Janczuk在silverlight電視上的演示來創建了一個使用WCF Duplex Polling Web服務的聊天程序。 客戶端訂閱服務器,然后服務器向所有連接的客戶端啟動通知以發布事件。
這個想法很簡單,在客戶端上,有一個按鈕可以讓客戶端進行連接。 客戶端可以在其中編寫消息並將其發布的文本框,以及用於顯示從服務器接收到的所有通知的更大的文本框。
我連接了3個客戶端(在不同的瀏覽器中,即IE,Firefox和Chrome),它們都運行良好。 他們發送消息並順利接收它們。 當我關閉其中一個瀏覽器時,問題開始。 一旦一個客戶離開,其他客戶就會陷入困境。 他們停止接收通知。
我猜想服務器中遍歷所有客戶端並向其發送通知的循環卡在現在丟失的客戶端上。 我嘗試捕獲該異常並將其從客戶端列表中刪除(請參見代碼),但仍然無濟於事。
有任何想法嗎?
服務器代碼如下:
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;
using System.Runtime.Remoting.Channels;
namespace ChatDemo.Web
{
[ServiceContract]
public interface IChatNotification
{
// this will be used as a callback method, therefore it must be one way
[OperationContract(IsOneWay=true)]
void Notify(string message);
[OperationContract(IsOneWay = true)]
void Subscribed();
}
// define this as a callback contract - to allow push
[ServiceContract(Namespace="", CallbackContract=typeof(IChatNotification))]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class ChatService
{
SynchronizedCollection<IChatNotification> clients = new SynchronizedCollection<IChatNotification>();
[OperationContract(IsOneWay=true)]
public void Subscribe()
{
IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>();
this.clients.Add(cli);
// inform the client it is now subscribed
cli.Subscribed();
Publish("New Client Connected: " + cli.GetHashCode());
}
[OperationContract(IsOneWay = true)]
public void Publish(string message)
{
SynchronizedCollection<IChatNotification> toRemove = new SynchronizedCollection<IChatNotification>();
foreach (IChatNotification channel in this.clients)
{
try
{
channel.Notify(message);
}
catch
{
toRemove.Add(channel);
}
}
// now remove all the dead channels
foreach (IChatNotification chnl in toRemove)
{
this.clients.Remove(chnl);
}
}
}
}
客戶端代碼如下:
void client_NotifyReceived(object sender, ChatServiceProxy.NotifyReceivedEventArgs e)
{
this.Messages.Text += string.Format("{0}\n\n", e.Error != null ? e.Error.ToString() : e.message);
}
private void MyMessage_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.client.PublishAsync(this.MyMessage.Text);
this.MyMessage.Text = "";
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.client = new ChatServiceProxy.ChatServiceClient(new PollingDuplexHttpBinding { DuplexMode = PollingDuplexMode.MultipleMessagesPerPoll }, new EndpointAddress("../ChatService.svc"));
// listen for server events
this.client.NotifyReceived += new EventHandler<ChatServiceProxy.NotifyReceivedEventArgs>(client_NotifyReceived);
this.client.SubscribedReceived += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_SubscribedReceived);
// subscribe for the server events
this.client.SubscribeAsync();
}
void client_SubscribedReceived(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
try
{
Messages.Text += "Connected!\n\n";
gsConnect.Color = Colors.Green;
}
catch
{
Messages.Text += "Failed to Connect!\n\n";
}
}
和Web配置如下:
<system.serviceModel>
<extensions>
<bindingExtensions>
<add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</bindingExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<pollingDuplex>
<binding name="myPollingDuplex" duplexMode="MultipleMessagesPerPoll"/>
</pollingDuplex>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service name="ChatDemo.Web.ChatService">
<endpoint address="" binding="pollingDuplex" bindingConfiguration="myPollingDuplex" contract="ChatDemo.Web.ChatService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
嘗試設置inactivityTimeout。 以前有同樣的問題。 為我工作。 pollingDuplex inactivityTimeout = “ 02:00:00” serverPollTimeout =“ 00:05:00” maxPendingMessagesPerSession =“ 2147483647” maxPendingSessions =“ 2147483647” duplexMode =“ SingleMessagePerPoll”
好的,我終於找到了解決方案。 它是一個骯臟的補丁程序,但是它可以正常工作並且穩定,因此我將使用它。
首先,我想澄清一下情況本身。 我以為這是一個僵局,但事實並非如此。 實際上,這是2個不同問題的結合,使我認為客戶機在服務器卡住某些東西時都在等待。 服務器沒有卡住,只是處於一個非常漫長的過程之中。 事實是,IE客戶端有一個自身的問題,這似乎使它永遠等待着。
我最終設法隔離了這兩個問題,然后為每個問題提供了自己的解決方案。
問題編號1:在嘗試向斷開連接的客戶端發送通知時,服務器掛起了很長時間。
由於這是循環完成的,因此其他客戶端也必須等待:
foreach (IChatNotification channel in this.clients)
{
try
{
channel.Notify(message); // if this channel is dead, the next iteration will be delayed
}
catch
{
toRemove.Add(channel);
}
}
因此,為解決此問題,我使循環為每個客戶端啟動了一個不同的線程,從而使客戶端的通知變得獨立。 這是最終代碼:
[OperationContract(IsOneWay = true)]
public void Publish(string message)
{
lock (this.clients)
{
foreach (IChatNotification channel in this.clients)
{
Thread t = new Thread(new ParameterizedThreadStart(this.notifyClient));
t.Start(new Notification{ Client = channel, Message = message });
}
}
}
public void notifyClient(Object n)
{
Notification notif = (Notification)n;
try
{
notif.Client.Notify(notif.Message);
}
catch
{
lock (this.clients)
{
this.clients.Remove(notif.Client);
}
}
}
請注意,有一個線程來處理每個客戶端通知。 如果客戶端發送通知失敗,該線程還將丟棄客戶端。
問題編號2:客戶端在閑置10秒鍾后終止連接。
出乎意料的是,這個問題只發生在資源管理器中...我無法真正解釋它,但是在Google進行了一些研究后,我發現我不是唯一注意到它的人,但是除了顯而易見的問題之外,找不到任何干凈的解決方案-“僅每9秒對服務器執行一次ping操作”。 這正是我所做的。
因此,我擴展了合同接口以包括服務器Ping方法,該方法立即調用客戶端的Pong方法:
[OperationContract(IsOneWay = true)]
public void Ping()
{
IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>();
cli.Pong();
}
客戶端的Pong事件處理程序創建一個睡眠9秒的線程,然后再次調用ping方法:
void client_PongReceived(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
// create a thread that will send ping in 9 seconds
Thread t = new Thread(new ThreadStart(this.sendPing));
t.Start();
}
void sendPing()
{
Thread.Sleep(9000);
this.client.PingAsync();
}
就是這樣。 我對多個客戶端進行了測試,通過關閉瀏覽器刪除了一些客戶端,然后重新啟動它們,一切正常。 丟失的客戶端最終被服務器清除。
還有一點需要注意的-由於客戶端連接被證明是不可靠的,因此我用try-catch異常將其包圍,因此我可以應對連接自發中斷的情況:
try
{
this.client.PublishAsync(this.MyMessage.Text);
this.MyMessage.Text = "";
}
catch
{
this.Messages.Text += "Was disconnected!";
this.client = null;
}
當然,這無濟於事,因為“ PublishAsync”會立即成功返回,而自動生成的代碼(在Reference.cs中)執行了在另一個線程中將消息發送到服務器的實際工作。 我能想到的捕獲此異常的唯一方法是更新自動生成的代理...這是一個非常糟糕的主意...但是我找不到其他方法。 (想法將不勝感激)。
就這樣。 如果有人知道解決此問題的簡便方法,我將非常高興聽到。
干杯,
科比
解決問題1的更好方法是使用異步模式設置回調:
[OperationContract(IsOneWay = true, AsyncPattern = true)]
IAsyncResult BeginNotification(string message, AsyncCallback callback, object state);
void EndNotification(IAsyncResult result);
當服務器通知其余客戶端時,它將發出前半部分:
channel.BeginNotification(message, NotificationCompletedAsyncCallback, channel);
這樣,其余的客戶端將收到通知,而不必等待已中斷的客戶端的超時。
現在將靜態完成方法設置為
private static void NotificationCompleted(IAsyncResult result)
在此完成的方法中,調用剩余的一半,如下所示:
IChatNotification channel = (IChatNotification)(result.AsyncState);
channel.EndNotification(result);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.