2010-08-12 7 views
18

Rinnovare di nuovo la quantità perché ho davvero bisogno di sapere come farlo funzionare o una risposta definitiva sul perché non lo farà.Ricerca WCF Duplex "TwoWay" Iscriviti + Callback Esempio

I've added an alternative explanation of the problem here.

Avere un sacco di tempo ottenere un doppio senso client-server WCF (IsOneWay = false) di lavorare in .Net 3/3.5.

Dopo che il client si è registrato con successo con il servizio, il servizio di periodico Annuncio() richiama i client registrati. È ora che il client o il server si blocca fino a quando il SendTimeout del server, regolato su 2 secondi, scade. Quindi il lato server ha un'eccezione di timeout come segue. Solo allora il codice utente del client riceve immediatamente la RICHIESTA METODO e tenta di restituire un valore. A quel punto il socket del client viene interrotto e la roba WCF fallisce.

Mi sembra che qualcosa sul client stia sospendendo la sua coda WCF locale dall'elaborazione fino a quando il socket scade, ma non abbastanza presto da annullare la chiamata al metodo locale. Ma se l'eccezione sotto è da credere, il server sta tentando di inviare un'operazione a http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous (inappropriata!) E sta scadendo. Forse quell'URI è solo il "Nome" del client connesso in remoto poiché WCF sa di farvi riferimento per gli scopi del messaggio di errore e sembra solo indicare che non sta caricando un URI. Non riesco a capire se il server fallisce per primo o se il client fallisce per primo.

Ho provato ad aggiungere traccia WCF ma non ho molte più informazioni.

Similar sample code is here, ma deve essere stato troppo da digerire. Ho sperimentato le varazioni di quel codice.

TimeoutException 'This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.' 

Server stack trace: 
    at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout) 
    at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout) 
    at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 
    at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 
    at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) 
+0

Questo è per 3.0/3.5, non 4.0. Grazie. –

+1

hai usato il trace wcf sia sul client che sul server per vedere se mostra qualcosa? –

+0

Sì, come meglio posso interpretare i risultati, trovo solo la stessa eccezione di cui sopra. Tutti gli ordini e i messaggi appaiono corretti in caso contrario. –

risposta

27

Innanzitutto procuratevi una copia di Programming WCF Services, se non ne hai già uno.

Se il client è WinForm o WPF, è necessario utilizzare [CallbackBehavior(UseSynchronizationContext = false)] altrimenti il ​​client non elaborerà il messaggio in arrivo finché il thread dell'interfaccia utente non entrerà nel ciclo dei messaggi.

In primo luogo, un canale "Duplex" in WCF non è realmente Duplex! Un messaggio da

  • client a server
  • possibile bloccare un messaggio il server è in attesa dal client
  • (o viceversa)

come messaggi vengono inviati solo in ordine su un singolo canale WCF. Un canale Duplex WCF NON fornisce due code di messaggi in entrata. I risultati che arrivano da una chiamata "TwoWay" sono uguali alla "chiamata" di questo livello dello stack WCF. Una volta capito questo, molti problemi diventano più comprensibili.

Se il client è WinForm o WPF, potrebbe essere necessario utilizzare [CallbackBehavior(UseSynchronizationContext = false)] altrimenti il ​​client non elaborerà il messaggio in arrivo finché il thread dell'interfaccia utente non entrerà nel ciclo dei messaggi.

Alcune regole che ho trovato per aiutare a evitare i deadlock. (Guarda le mie domande sulla WCF per vedere il dolore che ho avuto!)

Il sever non deve mai chiamare a un client sulla stessa connessione come una chiamata dallo stesso client è in processo su.

E/o

Il cliente non deve mai richiamare al server sulla stessa connessione come è utilizzato per le “callback”, mentre l'elaborazione di un call-back.

La prossima volta che penso utilizzerò solo due contratti (e quindi connessioni TCP) uno per il callback e altro per tutte le richieste client-> server. O usare il mio sistema di votazione, dato che mi ha dato così tanto dolore.

Scusa, non ho tempo per scrivere un esempio oggi. In ogni caso la maggior parte degli esempi funziona per quello che l'esempio sta tentando di fare, ma si scompone nella vita reale per qualche motivo che riguarda la tua applicazione.

Il miglior sito Web che conosco per gli esempi WCF è Juval Lowy’s web site.

È anche possibile trovare il questions I asked about WCF on Stack Overflow utile, poiché avevo lo stesso tipo di problemi.

Anche passare un giorno o due a leggere tutte le domande e le risposte di WCF su Stack Overflow darà una buona idea dei problemi da evitare.

+0

Ciao Ian, grazie per tutti questi suggerimenti. Il codice della domanda SO relativa ha UseSynchronizationContext = false e non richiama nel contesto dell'elaborazione di una chiamata: il client si iscrive alle sottoscrizioni, quindi in seguito il server chiede a ciascun cliente di sottoscrizione una semplice domanda ogni Timer.Tick. Continuerò a guardare anche i collegamenti suggeriti. –

+0

Il termine corretto per "non realmente duplex" è "half-duplex". –

+0

Grazie mille per una spiegazione dettagliata !! :) – Ross

4

Supponendo che il client sia un'applicazione WinForms, è necessario rendere la gestione del callback indipendente dal resto dell'applicazione utilizzando il suggerimento di Ian e delegando il lavoro da eseguire sul thread dell'interfaccia utente, se necessario. Ad esempio, se il server vuole informare il cliente di qualcosa, come ad esempio modificare il testo di un'etichetta, è possibile effettuare le seguenti operazioni:

[CallbackBehavior(UseSynchronizationContext = false)] 
internal class ServiceCallback : IServiceCallback 
{ 
    ChangeMainFormLabel(string text) 
    { 
     frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text)); 
    } 
} 

(Instance è una struttura statica che restituisce la singola istanza di frmMain e lblSomething è un po 'Label che il server vorrebbe cambiare.) Questo metodo restituirà immediatamente e libererà il server dall'attesa dell'interfaccia utente del client e l'interfaccia utente verrà aggiornata non appena sarà libera di farlo. E soprattutto, non ci sono deadlock perché nessuno sta aspettando nessuno.

+0

Si tratta di un'app Console. Ho provato a eseguire il ciclo principale su un thread di lavoro, ma non sembra aver cambiato nulla. Tutto ciò che si adatta alle tue idee? –

+1

@ uosɐs: se si tratta di un'app console, tutto ciò che devi fare è usare "UseSynchronizationContext = false". Ricorda che i metodi sull'oggetto callback verranno eseguiti su un thread diverso, quindi sincronizza qualsiasi accesso agli oggetti condivisi. Se pubblichi un esempio di codice, potrebbe aiutarci a individuare il problema che stai riscontrando. –

+0

[Qui] (http://stackoverflow.com/questions/3392123/wcf-duplex-callback-sample-failing) è l'esempio di codice che ha synccontext = false come suggerisci.(Ce l'ho nel post originale ma capisco che è difficile identificarlo) –

0

Siamo spiacenti, ho completamente dimenticato l'esempio (: - $).

Ecco il mio codice per il server:
ISpotifyServer.cs

[ServiceContract(CallbackContract = typeof(ISpotifyCallback))] 
public interface ISpotifyService 
{ 
    [OperationContract(IsOneWay = true)] 
    void Login(string username, string password); 
} 

ISpotifyCallback.cs

[ServiceContract] 
public interface ISpotifyCallback 
{ 
    [OperationContract(IsOneWay = true)] 
    void OnLoginComplete(); 

    [OperationContract(IsOneWay = true)] 
    void OnLoginError(); 
} 

Program.cs

class Program 
{ 
    static void Main(string[] args) 
    { 

     using (ServiceHost host = new ServiceHost(typeof(SpotifyService))) 
     { 
      host.Open(); 

      Console.WriteLine("Service running."); 
      Console.WriteLine("Endpoints:"); 

      foreach (ServiceEndpoint se in host.Description.Endpoints) 
       Console.WriteLine(se.Address.ToString()); 


      Console.ReadLine(); 

      host.Close(); 
     } 
    } 
} 

AppData.xml

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
    <system.serviceModel> 
    <behaviors> 
     <serviceBehaviors> 
     <behavior name="MetadataEnabledBehavior"> 
      <serviceMetadata /> 
      <serviceDebug includeExceptionDetailInFaults="True"/> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <services> 
     <service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService"> 
     <host> 
      <baseAddresses> 
      <add baseAddress="net.tcp://localhost:9821" /> 
      </baseAddresses> 
     </host> 
     <clear /> 
     <endpoint address="spotiserver" binding="netTcpBinding" 
      name="TcpEndpoint" contract="SpotiServer.ISpotifyService" 
      listenUriMode="Explicit"> 
      <identity> 
      <dns value="localhost"/> 
      <certificateReference storeName="My" storeLocation="LocalMachine" 
       x509FindType="FindBySubjectDistinguishedName" /> 
      </identity> 
     </endpoint> 
     <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" /> 
     </service> 
    </services> 
    </system.serviceModel> 
</configuration> 

E per il cliente:
Program.cs

class Program 
{ 
    static void Main(string[] args) 
    { 
     InstanceContext context = new InstanceContext(new CallbackHandler()); 

     String username; 
     String password; 

     Console.Write("Username: "); 
     username = Console.ReadLine(); 

     Console.WriteLine("Password: "); 
     password = ReadPassword(); 

     SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context); 
     client.Login(username, password); 

     Console.ReadLine(); 
    } 

    private static string ReadPassword() 
    { 
     Stack<string> passbits = new Stack<string>(); 
     //keep reading 
     for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true)) 
     { 
      if (cki.Key == ConsoleKey.Backspace) 
      { 
       //rollback the cursor and write a space so it looks backspaced to the user 
       Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); 
       Console.Write(" "); 
       Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); 
       passbits.Pop(); 
      } 
      else 
      { 
       Console.Write("*"); 
       passbits.Push(cki.KeyChar.ToString()); 
      } 
     } 
     string[] pass = passbits.ToArray(); 
     Array.Reverse(pass); 
     return string.Join(string.Empty, pass); 
    } 
} 

penso questo è tutto. Ho anche un'implementazione delle interfacce, che (sul lato client) stampa il risultato sulla console e sul server esegue "OnLoginComplete" se user-name e password sono corretti, altrimenti esegue "OnLoginError". Fammi sapere se non funziona o se hai bisogno di aiuto per impostare tutto.

+1

Ciao Alxandr. Apprezzo il tempo che hai impiegato per postare questo, ma IsOneWay = true è il problema. Ho avuto successo con tale IsOneWay = veri esempi. È l'IsOneWay = falso che è la sfida e lo scopo di questa domanda. –

+0

Stai usando prese o http? – Alxandr

+0

Un canale nettcp –

Problemi correlati