2011-01-10 15 views
5

Silverlight TVでTomek Janczukのデモンストレーションを行い、WCF Duplex Polling Webサービスを使用するチャットプログラムを作成しました。クライアントはサーバーにサブスクライブし、サーバーは接続されたすべてのクライアントにイベントをパブリッシュするための通知を開始します。SilverlightでWCFデュプレックスポーリングを使用するとデッドロックが発生する

アイデアは簡単ですが、クライアントにはクライアントが接続できるボタンがあります。クライアントがメッセージを書き込んで発行できるテキストボックス、およびサーバーから受信したすべての通知を表示する大きなテキストボックス。

私は3つのクライアント(IE、Firefox、Chromeの異なるブラウザ)を接続していて、すべてうまく動作します。メッセージを送信してスムーズに受信します。この問題は、ブラウザの1つを閉じると始まります。 1人のクライアントが出るとすぐに、他のクライアントは立ち往生します。彼らは通知を受けるのをやめます。

私は、すべてのクライアントを経由して通知を送信するサーバーのループが、現在失われているクライアントで停止していると推測しています。私は例外をキャッチして、クライアントリスト(コードを参照)から削除しようとしましたが、それでも助けにはなりません。

次のように

サーバー・コードは次のとおりです。

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構成は次のとおりです:

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); 
      } 
     } 
    } 
} 

次のようにクライアントコードがある

<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> 
+0

:このような呼び出しの残りの半分を呼び出す今、この完成方法で

private static void NotificationCompleted(IAsyncResult result) 

ような方法を完了し、静的に設定

スレッド化されています。そのため、ループ内のクライアントに通知するのではなく、クライアントごとにスレッドを作成し、スレッドにクライアントに通知させるので、一方のクライアントはもう一方のクライアントを待機する必要はありません。それはその問題を解決するようだが、まだ奇妙な問題がある。クライアントがIEで動作しているときに、10秒間アクティビティがない場合、接続は切断されています。次にメッセージを送信しようとすると、接続がフォールト状態であることを示す例外が発生します。これはIEでのみ発生します...誰も何か考えている? –

答えて

2

OK、私は最終的に解決策を見つけました。そのような汚れたパッチのようなものですが、動作し安定していますので、これを使用します。

まず、状況そのものを明確にしたいと思います。私はこれがデッドロックだと思ったが、そうではなかった。実際には2つの異なる問題の組み合わせによって、サーバーが何かにぶつかっている間にクライアントがすべて待っていると思うようになりました。サーバーは立ち往生していませんでした。ちょうど非常に長いプロセスの途中でした。 IEクライアントにはそれ自体の問題があり、永遠に待っていたように見えます。

私は最終的に2つの問題を分離して管理し、それぞれの問題に独自の解決策を与えました。

問題番号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); 
     } 
    } 
} 

各クライアント通知を処理するスレッドは1つあります。通知を送信できなかった場合、スレッドはクライアントも破棄します。

問題番号2:クライアントは、10アイドル秒後に接続を終了します。

驚くべきことに、この問題はエクスプローラでのみ発生しました...私は実際には説明できませんが、Googleでいくつかの調査をしたところ、 "9秒ごとにサーバーにpingを送信する"という明白な点を除けば、きれいなソリューションです。それはまさに私がやったことです。

だから私は即座に、クライアントのポンメソッドを呼び出して、サーバーに対してpingを実行する方法を含むように契約のインターフェイスを拡張:

[OperationContract(IsOneWay = true)] 
public void Ping() 
{ 
    IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>(); 
    cli.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 
     { 
      this.client.PublishAsync(this.MyMessage.Text); 
      this.MyMessage.Text = ""; 
     } 
     catch 
     { 
      this.Messages.Text += "Was disconnected!"; 
      this.client = null; 
     } 

これ、当然のことながら、 "PublishAsync"が即座に戻り、自動的に生成されたコード(Reference.cs)が別のスレッドでサーバーにメッセージを送信する実際の作業を行います。私がこの例外をキャッチすると考えることができる唯一の方法は、自動的に生成されたプロキシを更新することです。これは非常に悪い考えです...しかし、私は他の方法を見つけることができませんでした。 (アイデアは高く評価されます)。

それだけです。誰かがこの問題を回避するためのより簡単な方法を知っていれば、私は聞いて嬉しくなるでしょう。

[OperationContract(IsOneWay = true, AsyncPattern = true)] 
    IAsyncResult BeginNotification(string message, AsyncCallback callback, object state); 
    void EndNotification(IAsyncResult result); 

サーバが発行し、残りのクライアントに通知:

乾杯、

+0

あなた自身がスレッドの作成を扱うのではなく、クライアントにコールバックしているときは、頼りにすることをお勧めしますThreadPoolシンプルなコードなので、多すぎるスレッドでシステムが過負荷になることを心配する必要はありません。また、Asyncコールバックモデルを使用することもできますが、コードはかなり複雑です。実際には推奨されません。 10秒間何も操作しなくてもIE接続が正常に動作しない場合、WebRequest.RegisterPrefix()を使用して、Silverlightにブラウザの内部HTTPスタックを使用するよう指示したことがありますか? –

+1

こんにちはケン、あなたのコメントのthnx。これらの2つのトピックについて詳しく説明してください:threadPoolと内部HTTPスタック対ブラウザ?私はそれについてもっと聞くのが大好きです。 –

2

は設定してくださいinactivityTimeout。前に同じ問題があった。私のために働いた。 pollingDuplex inactivityTimeout = "二時00分00秒" serverPollTimeout = "午後12時05分00秒" maxPendingMessagesPerSession = "2147483647" maxPendingSessions = "2147483647" duplexMode = "SingleMessagePerPoll"

+1

答えはThnxですが、わかりません。これらの値はどこで設定しますか?クライアント上またはサーバー上で? –

+0

これは私が持っているものです。 <バインディング名= "PollingDuplexBindingServer"> msqsf

1

コビ問題#1を解決するためのより良い方法は、非同期パターンを使用してコールバックを設定することです前半:

channel.BeginNotification(message, NotificationCompletedAsyncCallback, channel); 

このようにして、残りのクライアントはnotifiドロップダウンしたクライアントのタイムアウトを待たずに実行できます。私は通知マルチを作ることによって、この問題を回避しようとした

IChatNotification channel = (IChatNotification)(result.AsyncState); 
    channel.EndNotification(result); 
関連する問題