Laden...

WCF: Gleichzeitige Übertragung von Nachrichten über mehrere Callback-Kanäle

Erstellt von DonC vor 13 Jahren Letzter Beitrag vor 13 Jahren 6.661 Views
D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren
WCF: Gleichzeitige Übertragung von Nachrichten über mehrere Callback-Kanäle

Hi Leutz,

ich habe derzeit ein kleines Problem bei der Entwicklung eines WCF-Services, welcher über Callbacks Nachrichten an die angemeldeten Clients überträgt. Das wichtige dabei: Die übertragung der Nachricht an die Clients soll parallel, d.h. in mehreren Threads erfolgen. Sinn und Zweck ist es, dass wenn ein Client aus was für Gründen auch immer abstürzt, die Übermittlung der Nachricht an die anderen Clients für die Dauer des Verbindungs-Timeouts nicht blockiert wird. Die Übertragung erfolgt übrigens über ein NetTcpBinding.

Der Service-Kontrakt ist folgendermaßen implementiert (Auszug):


[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple, MaxItemsInObjectGraph = 2147483647, IncludeExceptionDetailInFaults = true)]
public class Server : IServer
{
   // Liste der momentan verbundenen Clients
   private Dictionary<IClient, string> connected_frontends = new Dictionary<IClient, string>();

   //Hier registriert sich ein neuer Client beim Server um von diesem in Zukunft Nachrichten zu empfangen 
   public void register(string id)
   {
        IClient callback = OperationContext.Current.GetCallbackChannel<IClient>();

         lock (this.connected_frontends)
         {
               if (!this.connected_frontends.ContainsKey(callback))
               {
                   this.connected_frontends.Add(callback, id);
               }
          }

          callback.OnRegister();            
    }

    private delegate void d_sendMessage(IClient client, string message);
        
    public void sendMessageToClients(string message)
    {
         Dictionary<IClient, string> frontends = new Dictionary<IClient, string>(this.connected_frontends);

         foreach (IClient frontend in frontends.Keys)
         {
             //Übermittlung der Nachricht wird für jeden Callback-Kanal in einen separaten Thread ausgelagert
             d_sendMessage d = new d_sendMessage(thread_sendMessageToClients);
             d.DynamicInvoke(new object[] { frontend, message });
         }
   }
        
   private void thread_sendMessageToClients(IClient frontend, string message)
   {
            try
            {
                //Übermittlung der Nachricht an den verbundenen Client
                frontend.OnMessage(message);
            }
            catch 
            {
                //Hier wird der Callback-Kanal aus der Liste der verbundenen Clients entfernt
            }
    }
}

Das Problem liegt in der Methode "sendMessageToClients". Angenommen es haben sich zwei Clients beim Service angemeldet, d.h. es sind zwei Callback-Kanäle in der Liste (connected_frontends) gespeichert. Wenn nun der erste Client abstürzt und der dazugehörige Callback-Kanal somit nicht mehr funtionsfähig ist, wird die zu übermittelnde Nachticht an den zweiten, noch verbundenen Client erst dann übertragen, wenn der Verbindungs-Timeout für den zerstörten Callback-Kanal abgelaufen ist. Und das, obwohl ich die Übermittlung der Nachricht an einen Client je in einen separaten Thread ausgelagert habe. Wieso ist das so? Übersehe ich irgendwas? Ich möchte wie schon gesagt, dass die Nachricht an beide verbundenen Client gleichzeitig übertragen wird und im Falle eines Verbindungsabbruchs bei einem Client die Übertragung der Nachricht an die anderen Clients nicht vorrübergehend blockiert wird.

Es wäre super, wenn Ihr mir da helfen könntet.

Gruß,
DonC

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

eine Begründung wieso das so ist kann ich dir jetzt nicht geben (ich habs vergessen 😉 aber mit dem AsyncPattern sollte es paralleler sein.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Also so wirklich schlau werde ich da gerade nicht draus 🙁
Hast Du in diesem Zusammenhang vielleicht ein Codebeispiel?

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Codebeispiele hierzu (für das Async-Pattern) gibt in der MSDN eh genug (zumindest 1 😉. Das umzuändern für den Client-Aufruf dürfte kein Problem sein. Probiers mal und wenn du nicht weiter weißt melde dich wieder.

Bitte beachte auch [Hinweis] Wie poste ich richtig? Punkt 4.b

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

J
1.114 Beiträge seit 2007
vor 13 Jahren

Um diesem Problem aus dem Weg zu gehen sind Operationen in meinen Callback Verträge immer mit dem IsOneWay Attribut versehen. Dadurch funktioniert der Server nach dem Prinzip "Fire and Forget". Timeouts treten somit in meinen nicht auf.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

"Fire and Forget"

Noch als Ergänzung: Ganz asynchron wird es dabei nicht da du Aufgaben in eine Queue gestellt werden müssen und erst wenn das Enqueue erfolgreich erledigt ist dann gilt "Forget" - es wird keine Response-Message generiert. Dauert das Enqueue zu lange könnte auch ein Timeout auftreten - aber ich denke das ist i.d.R. sehr selten.

Hingegen das AsyncPattern ist wirklich Fire & Forget (das Thread-Enqueue mal außer Acht gelassen). Allerdings auch ein wenig aufwändiger 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Besten Dank 😃
Hab mich jetzt ein wenig in das Thema eingelesen und werde das morgen mal testen. Aber an sich hört sich das ganze schonmal vielversprechend an. Werde dann berichten 😃

Greetz,
DonC

J
1.114 Beiträge seit 2007
vor 13 Jahren

Dauert das Enqueue zu lange könnte auch ein Timeout auftreten - aber ich denke das ist i.d.R. sehr selten.

Kannst du mir ein Scenario schildern, bei dem ein Enqueue Probleme macht. Ich hatte zumindest noch keine negativen Erfahrungen damit.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Jelly,

ich habs selbst auch noch nicht beobachtet 😉

Die Möglichkeit besteht jedoch.

Zitat von: IsOneWay
Specifying that an operation is a one-way operation means only that there is no response message. It is possible to block if a connection cannot be made, or the outbound message is very large, or if the service cannot read inbound information fast enough. If a client requires a non-blocking call, generate AsyncPattern operations.

In One-Way Services gibts einen ganzen Abschnitt zu dem Thema.

Wikliches Fire&Forget gibt es nur mit dem Async-Pattern (BeginXXX/EndXXX).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

J
1.114 Beiträge seit 2007
vor 13 Jahren

Danke für das Zitat.

Wichtig zu wissen ist es allemal.

Wie ich das sehe gibt es also 3 Möglichkeiten wann ein OnWay Aufruf trotzdem blockieren kann:[1.]Verbindung zum Client kann nicht hergestellt werden
[2.]Zu grosse Objekte werden zum Client verschickt
[3.]Der Server kann irgendwelche Informationen nicht schnell genug lesen

zu 1.:
Die Verbindung sollte eigentlich stehen, es sei denn es gab mal einen Netzwerkaussetzer o.ä. Das kann natürlich immer mal passieren.

zu 2.: Da ich eigentlich den Client vom Server nur anstupse um ihm mitzuteilen, da ist was das dich interessiert, kann das bei meinen Fällen nicht eintreten

zu 3.: Auch das tritt nicht ein, da ja lediglich ein besseres Ping zum Client geschickt wird.

Punkt 1. kann aber allerdings in der Tat zu einem Problem führen.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Jelly,

da teile ich deine Einschätzung dass eigentlich nur Punkt 1 relevant ist.

IsOneWay ist ja praktisch, aber man sollte die (theoretischen) Gefahren wenigsten kennen 😉.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Punkt 1. kann aber allerdings in der Tat zu einem Problem führen.

Hm, aber das ist ja genau das Problem, welches ich vermeiden möchte. Wenn mehrere Clients beim Server angemeldet sind und die Netzwerkverbindung zu einem Client abbricht, werden also die anderen Clients so lange blockiert bis beim getrennten Client der Timeout abgelaufen ist? Ich dachte, genau so etwas würde sich mit AsyncPattern vermeiden lassen?

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

genau so etwas würde sich mit AsyncPattern vermeiden lassen?

Ja stimmt, der Exkurs von Jelly bezog sich aber auf IsOneWay und das ist nicht das AsyncPattern.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Alles klar 😃

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Also ich habe mich jetzt an die Implementierung gemacht, aber irgendwas muss ich übersehen. In meinem Kontrakt sind jetzt folgende Methoden definiert:



[OperationContract(IsOneWay = true)]
void OnMessage(string message);

[OperationContractAttribute(AsyncPattern = true)]
IAsyncResult BeginASOnMessage(string message, AsyncCallback callback, object asyncState);

void EndASOnMessage(IAsyncResult result);


Diese Methoden werden in der Server-Klasse folgendermaßen implementiert:



public void OnMessage(string message)
{
     //Hier soll jetzt die Methode "BeginASOnMessage" asynchron ausgeführt werden und die "OnMessage"-Methode sofort verlassen werden (halt Fire&Forget)
     BeginASOnMessage(message, new AsyncCallback(EndASOnMessage), null);
}

public IAsyncResult BeginASOnMessage(string message, AsyncCallback callback, object asyncState)
{
     //Hier werden die Funktionen auf den Callbacks aufgerufen
            
     return new CompletedAsyncResult<string>("Completed");
}

public void EndASOnMessage(IAsyncResult r)
{
     Console.WriteLine("Completed ... ");            
}


Andere Klassen rufen nun die Methode "OnMessage" auf, in der ja nun die weiteren Aktionen durch den Aufruf von "BeginASOnMessage" asynchron ausgeführt werden sollen. Das Problem ist, "BeginASOnMessage" läuft anscheinend nicht asynchron, da nach wie vor die Clients von einem getrennten Client blockiert werden und während der Laufzeit wird auch nicht die Methode "EndASOnMessage" aufgerufen.
Was mach ich falsch?

Die "CompletedAsyncResult"-Klasse habe ich übrigens aus der MSDN. Aber die sollte hier kein Problem darstellen:


class CompletedAsyncResult<T> : IAsyncResult
    {
        T data;

        public CompletedAsyncResult(T data)
        { this.data = data; }

        public T Data
        { get { return data; } }

        #region IAsyncResult Members
        public object AsyncState
        { get { return (object)data; } }

        public WaitHandle AsyncWaitHandle
        { get { throw new Exception("The method or operation is not implemented."); } }

        public bool CompletedSynchronously
        { get { return true; } }

        public bool IsCompleted
        { get { return true; } }
        #endregion
    }

Greetz,
DonC

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

der Client sollte die BeginXXX-Methode aufrufen. Wenn wie in deinem Code die synchrone Methode an die BeginXXX synchron delegiert dann ist auch der Aufruf synchron.

Oder hab ich deinen Code falsch interpretiert?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Naja, also wenn aus der synchronen Methode "OnMessage" die asynchrone Methode "BeginASOnMessage" aufgerufen wird, sollte die Abarbeitung des Codes in "BeginASOnMessage" asynchron erfolgen und die Methode "OnMessage" direkt mit der nächsten Anweisung weitermachen, was in diesem Fall der Beeindigung der Methode gleichkommt, unabhängig vom Ausführungsstand der Methode "BeginASOnMessage" oder?

Der Client selbst kann die Methode "BeginASOnMessage" nicht aufrufen, da der Server selbst die Nachrichten an die Clients übermitteln soll, d.h. die Clients wissen nicht, wann eine neue Nachricht vom Server eintrifft. Wenn der Server die Nachrichten an die verschiedenen Clients verschickt, soll das halt asynchron erfolgen 😃

Hinweis von gfoidl vor 13 Jahren

Bitte beachte auch [Hinweis] Wie poste ich richtig? Punkt 2.3

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

die BeginASOnMessage-Methode wird aber synchron von der OnMessage-Methode aufgerufen.

Hab jetzt fast vergessen dass es ein Callback sein soll 😉
Deshalb müssen die Begin/End-Methode im Callback-Contract stehen denn zu diesem Zeitpunkt ist der Client der eigentliche Server und der Server der eigentliche Client (also vertauschte Rollen). Bedenke das und ändere dein Beispiel dahingehend ab.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
DonC Themenstarter:in
30 Beiträge seit 2007
vor 13 Jahren

Klasse, so scheint es tatsächlich zu funktionieren 😃
Falls es doch noch Probleme geben sollte, meld ich mich wieder.

Auf jeden Fall erstmal ganz dollen Dank für die Hilfe 😃

Greetz,
DonC

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

schau dir auch mal Tracing bei WCF an - dort kannst du dir die Nachrichten protokollieren lassen und so eben verfolgen was passiert. Ist ganz einfach 😉

In der App.config des Services (und wegen Callback auf beim Client) einfach


<system.diagnostics>
    <sources>
        <source name="System.ServiceModel" switchValue="Warning">
            <listeners>
                <clear />
                <add name="ServiceModelTraceListener"
                        type="System.Diagnostics.XmlWriterTraceListener"
                        initializeData="Service.svclog"
                        traceOutputOptions="Timestamp" />
            </listeners>
        </source>
        <source name="System.ServiceModel.MessageLogging" switchValue="Warning">
            <listeners>
                <clear />
                <add name="ServiceModelMessgeLoggingTraceListener"
                        type="System.Diagnostics.XmlWriterTraceListener"
                        initializeData="Service.Messages.svclog"
                        traceOutputOptions="Timestamp" />
            </listeners>
        </source>
    </sources>

    <trace autoflush="true" />
</system.diagnostics>

einfügen. Nähere Infos im entsprechenden Abschnitt in der MSDN.

Kann dann mit dem Service Trace Viewer betrachtet werden. Normal ist die Dateiendung .svclog eh registiert so dass ein Klick darauf reicht.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"