2010-09-18 15 views
6

J'utilise un netNamedPipeBinding pour effectuer une communication WCF inter-processus entre une application Windows et un service Windows.Une exception WCF a été reçue lors de la fermeture de la connexion avec les rappels utilisés

Maintenant, mon application fonctionne bien dans tous les autres comptes (après avoir combattu ma juste part des exceptions WCF comme tout le monde qui a travaillé avec WCF le sait ..) mais cette erreur est celle qui s'avère très résiliente.

Pour peindre une image de mon scénario: mon service Windows pourrait être mis en file d'attente pour faire un travail à un moment donné via un bouton enfoncé dans l'application Windows et il parle ensuite sur le netNamedPipeBinding qui est une liaison qui prend en charge les rappels (deux -comparaison) si vous n'êtes pas familier et lance une requête pour effectuer ce travail, (dans ce cas une procédure de téléchargement de fichier), il lance également les rappels (événements) toutes les quelques secondes allant de la progression du fichier à la vitesse de transfert etc. à l'application Windows, il y a donc une certaine intégration client-serveur assez serrée; C'est ainsi que je reçois mes progrès de ce qui fonctionne dans mon service Windows dans mon application Windows. Maintenant, tout est super, les dieux de la WCF sont relativement heureux avec moi maintenant à part une mauvaise exception que je reçois chaque fois que je ferme l'application prématurément (ce qui est un scénario parfaitement valide). Alors qu'un transfert est en cours, et callbacks tirent assez fortement, je reçois cette erreur:

System.ServiceModel.ProtocolException: 
    The channel received an unexpected input message with Action 
    'http://tempuri.org/ITransferServiceContract/TransferSpeedChangedCallback' 
    while closing. You should only close your channel when you are not expecting 
    any more input messages. 

Maintenant, je comprends cette erreur, mais malheureusement je ne peux pas garantir de fermer ma chaîne après ne recevoir aucune messsages plus entrée, comme l'utilisateur peut arrêter l'application à tout moment donc le travail se poursuivra en arrière-plan du service Windows (un peu comme le fonctionnement d'un scanner de virus). L'utilisateur doit être en mesure de démarrer et de fermer l'application de l'outil de gestion des gains autant qu'ils le souhaitent sans interférence.

Maintenant l'erreur, je reçois immédiatement après avoir effectué mon appel Unsubscribe() qui est le deuxième appel avant de terminer l'application et ce que je crois est le moyen préféré pour déconnecter un client WCF. Tout ce désabonnement avant de fermer la connexion supprime simplement l'ID client d'un tableau stocké localement sur le service wcf de win service (car il s'agit d'une instance PARTAGÉE à la fois par le service win et l'application windows car le service win peut effectuer des tâches événements planifiés par lui-même) et après l'élimination du tableau d'identification client que j'effectue, ce que j'espère (sentir) devrait être une déconnexion propre. Le résultat de ceci, en plus de recevoir une exception, est que mon application se bloque, l'interface utilisateur est totalement bloquée, les barres de progression et tout à mi-chemin, avec tous les signes indiquant une condition de course ou un blocage de la WCF. mais je suis assez avisé maintenant et je pense que c'est une situation relativement isolée et en lisant l'exception telle qu'elle est, je ne pense pas que ce soit un problème en soi, car il mentionne plus une question de déconnexion précoce qui puis spirale tous mes fils dans le chaos, peut-être provoquer le verrouillage.

Mon approche Unsubscribe() sur le client ressemble à ceci:

public void Unsubscribe() 
    { 
     try 
     { 
      // Close existing connections 
      if (channel != null && 
       channel.State == CommunicationState.Opened) 
      { 
       proxy.Unsubscribe(); 
      } 
     } 
     catch (Exception) 
     { 
      // This is where we receive the 'System.ServiceModel.ProtocolException'. 
     } 
     finally 
     { 
      Dispose(); 
     } 
    } 

Et ma méthode Dispose(), qui doit effectuer la déconnexion propre:

public void Dispose() 
    { 
     // Dispose object 
     if (channel != null) 
     { 
      try 
      { 
       // Close existing connections 
       Close(); 
       // Attempt dispose object 
       ((IDisposable)channel).Dispose(); 
      } 
      catch (CommunicationException) 
      { 
       channel.Abort(); 
      } 
      catch (TimeoutException) 
      { 
       channel.Abort(); 
      } 
      catch (Exception) 
      { 
       channel.Abort(); 
       throw; 
      } 
     } 
    } 

Et le service WCF Subscription() contrepartie et classe attributs (pour référence) sur le service Windows serveur (rien de difficile ici et mon exception se produit côté client):

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple)] 
    public class TransferService : LoggableBase, ITransferServiceContract 
    { 
     public void Unsubscribe() 
     { 
      if (clients.ContainsKey(clientName)) 
      { 
       lock (syncObj) 
       { 
        clients.Remove(clientName); 
       } 
      } 

#if DEBUG 
      Console.WriteLine(" + {0} disconnected.", clientName); 
#endif 
     } 
     ... 
    } 

Interface de:

[ServiceContract(
    CallbackContract = typeof(ITransferServiceCallbackContract), 
    SessionMode = SessionMode.Required)] 
public interface ITransferServiceContract 
{ 
    [OperationContract(IsInitiating = true)] 
    bool Subscribe(); 

    [OperationContract(IsOneWay = true)] 
    void Unsubscribe(); 
    ... 
} 

Interface du contrat de rappel, il ne fait rien, juste appelle des événements très excitant par les délégués, etc. La raison pour laquelle j'ai inclus cela est de montrer vous mes attributs. Je l'ai fait alléger un ensemble de déjà en incluant les interblocages UseSynchronizationContext = false:

[CallbackBehavior(UseSynchronizationContext = false, 
ConcurrencyMode = ConcurrencyMode.Multiple)] 
public class TransferServiceCallback : ITransferServiceCallbackContract 
{ ... } 

espère vraiment que quelqu'un peut me aider! Merci beaucoup :) =

+0

Je ne sais pas sur la question spécifique, mais pour plus d'informations le snafu threading sons * peut-être * en raison de la façon dont WCF utilise la synchronisation contexte (ce qui est le via le formulaire en WinForms, etc.). –

+0

Merci Marc, yep qui m'a pris et j'ai atténué un ensemble de en lisant les interblocages sur cette question, l'affaire était de mettre sur le contrat de rappel 'UseSynchronizationContext = false';) Je vais ajouter à mes exemples. – GONeale

+0

ah, à droite; bon de vous voir déjà couvert, p –

Répondre

11

OH mon Dieu, je l'ai trouvé la question.

Cette exception n'a rien à voir avec l'application de suspendre certain Sous-jacent, qui était juste une exception de précaution que vous pouvez en toute sécurité attraper.

Vous ne le croiriez pas, j'ai passé environ 6 heures sur et en dehors de ce bug, il est avéré être le channel.Close() de verrouillage en attente de requêtes en attente de WCF pour terminer (ce qui ne serait jamais terminée tant que le transfert ait fini! Qui Je viens de faire un breakpointing brutal, ligne après ligne, mon problème était que si j'étais trop lent ... il ne serait jamais accroché, parce que d'une certaine façon le canal serait disponible pour fermer (même avant le transfert était terminé), j'ai donc dû faire un break à F5, puis rapidement faire un pas pour attraper le coup, et c'est la fin de la ligne. Je demande maintenant simplement une valeur de délai d'attente pour l'opération Close() et l'attraper avec un TimeoutException et dur avorte le canal si elle ne peut pas fermer en temps opportun!

voir le code fixe:

private void Close() 
{ 
    if (channel != null && 
     channel.State == CommunicationState.Opened) 
    { 
     // If cannot cleanly close down the app in 3 seconds, 
     // channel is locked due to channel heavily in use 
     // through callbacks or the like. 
     // Throw TimeoutException 
     channel.Close(new TimeSpan(0, 0, 0, 3)); 
    } 
} 

public void Dispose() 
{ 
    // Dispose object 
    if (channel != null) 
    { 
     try 
     { 
      // Close existing connections 
      // ***************************** 
      // This is the close operation where we perform 
      //the channel close and timeout check and catch the exception. 
      Close(); 

      // Attempt dispose object 
      ((IDisposable)channel).Dispose(); 
     } 
     catch (CommunicationException) 
     { 
      channel.Abort(); 
     } 
     catch (TimeoutException) 
     { 
      channel.Abort(); 
     } 
     catch (Exception) 
     { 
      channel.Abort(); 
      throw; 
     } 
    } 
} 

Je suis tellement heureux d'avoir ce bug enfin fini et bien fini! Mon application est maintenant en train de fermer proprement après un délai de 3 secondes, quel que soit l'état actuel du service WCF, j'espère que j'aurais pu aider quelqu'un d'autre qui se retrouve confronté à un problème similaire.

Graham

+0

_Cette exception n'avait rien à voir avec l'application underyling, c'était juste une exception de précaution Avez-vous un lien de référence qui parle de la sécurité d'avaler ProtocolExceptions comme ça? J'ai exactement le même problème. – lesscode