2010-06-14 5 views
8

(En supposant une méthode WCF appelée « MaFonction »)Quelle est la meilleure façon d'annuler une requête WCF asynchrone?

Actuellement, pour soutenir l'annulation d'une demande WCF, j'utilise les méthodes BeginMyFunction/de EndMyFunction générés par svcutil (et la manipulation d'un drapeau isCanceled lors de la répartition des résultats au principal fil). Je voudrais utiliser la méthode MyFunctionAsync (et l'ancrage dans l'événement MyFunctionAsyncCompleted à la place) pour les appels async au lieu de Begin/End. Quelle est la meilleure façon de traiter les demandes WCF en cas d'utilisation de MyFunctionAsyncCompleted, tout en veillant à ce que l'événement ne soit pas déclenché sur une page qui n'est plus chargée (c'est-à-dire la navigation dans une image).

Merci!

EDIT:

J'ai décidé que je veux créer mon objet WcfClient sur une base par appel (par opposition à par-WPF-page ou par application), voici donc ce que je J'ai trouvé:

public void StartValidation(){ 
    WcfClient wcf = new WcfClient(); 
    wcf.IsValidCompleted += new EventHandler<IsValidCompletedEventArgs>(wcf_IsValidCompleted); 
    //pass the WcfClient object as the userState parameter so it can be closed later 
    wcf.IsValidAsync(TextBox.Text, wcf); 
} 

void wcf_IsValidCompleted(object sender, IsValidCompletedEventArgs e) { 
    if(!m_IsCanceled){ 
     //Update the UI 
     //m_IsCanceled is set to true when the page unload event is fired 
    } 
    //Close the connection 
    if (e.UserState is WcfClient) { 
     ((WcfClient)e.UserState).Close(); 
    } 
} 

Je trouve difficile de trouver la méthode recommandée pour accomplir ce que je viens de mettre en œuvre. Est-ce que c'est bien tel quel, ou y a-t-il des pièges/des cas limites dont je dois m'inquiéter? Quelle est la norme d'or quand il s'agit d'annuler correctement un appel WCF?

+0

De cette façon, vous n'êtes pas annulez vraiment l'appel (c'est-à-dire qu'il est encore en cours d'exécution.) Vous vérifiez simplement si vous devez ignorer les résultats, non? – GuyBehindtheGuy

+0

Très vrai. Ce droit garantit qu'au moins mon interface utilisateur n'est pas mise à jour après le déchargement de la page. J'étudie la séquence d'appels que je devrais effectuer dans l'événement de déchargement (par exemple, Fermer, Abandonner, etc.) pour contourner cette technique de "marquage" que j'utilise actuellement. – Pwninstein

+0

L'appel de 'Abort' sur un déchargement n'annule pas toujours la requête (en fait, la requête se termine presque toujours, Fiddler le prouve). J'ai ajouté un délai de 5 secondes sur le serveur pour me donner le temps d'annuler l'appel. Ce n'est pas ce à quoi je m'attendrais. – Pwninstein

Répondre

6

Il n'existe aucun moyen d'annuler la requête asynchrone, sauf si vous créez manuellement les fonctions asynchrones. Considérant que vous générez automatiquement vos appels WCF qui en feraient plus d'une corvée. Même alors, comme vous avez dit que l'appel ne s'annule pas, il continuera à suivre son cours. Si vous voulez toujours annuler, vous devez simplement vous assurer que le client/l'interface utilisateur ignore les résultats de l'appel.

2

Annulation d'une requête n'est pas vraiment pratique dans le sens général simplement en raison de la nature asynchrone de la demande dont vous parlez. Des solutions de contournement spécifiques peuvent être effectuées. Par exemple, en ajoutant un nouveau signal d'annulation à votre protocole qui définit un état partagé que votre tâche primaire de longue durée vérifie périodiquement pour vérifier qu'elle doit continuer à s'exécuter.

Quoi qu'il en soit, encore une fois, en raison de la nature asynchrone de la requête, je crois qu'il est toujours de la responsabilité du client de savoir ignorer les résultats d'une requête qui a été annulée. Il n'est pas possible de garantir que le client ne recevra pas de réponse suite à une demande d'annulation, simplement parce qu'il existe une course entre le client et le serveur. Il est possible d'ajouter une couche supplémentaire qui surveille si quelque chose a été annulé et sait rejeter la réponse d'une requête, mais cela n'empêche pas le résultat d'être transmis au client.

1

Si je vous comprends bien, vous essayez d'interrompre correctement un appel en attente à un service WCF. Vous souhaitez utiliser l'événement MyFunctionCompleted car il est géré dans le thread UI.

Ce que vous devriez probablement faire est d'appeler la méthode Abort sur le WcfClient (vous devez garder une référence). Cela nettoiera les ressources du côté client. Le serveur va terminer la requête, mais le client ne l'attendra plus. Peu de temps après l'événement MyFunctionCompleted est déclenché. En vérifiant le client.State, vous saurez si l'appel a réussi, a échoué ou a été annulé.

Voici une petite application de test ayant un bouton Envoyer, un bouton Abandonner et une zone de texte pour les résultats:

public partial class MainForm : Form 
{ 
    public MainForm() 
    { 
     InitializeComponent(); 
    } 

    private SomeServiceClient m_client; 

    private void buttonSend_Click(object sender, EventArgs e) 
    { 
     m_client = new SomeServiceClient(); 
     m_client.MyFunctionCompleted += new EventHandler<MyFunctionCompletedEventArgs>(client_MyFunctionCompleted); 
     m_client.MyFunctionAsync(4000, m_client); 
    } 

    private void buttonAbout_Click(object sender, EventArgs e) 
    { 
     if(m_client != null) 
      m_client.Abort(); 
    } 

    void client_MyFunctionCompleted(object sender, MyFunctionCompletedEventArgs e) 
    { 

     var client = e.UserState as SomeServiceClient; 
     if (client != null) 
     { 
      if (client.State == System.ServiceModel.CommunicationState.Opened) 
      { 
       textBox.Text += string.Format("Result: {0}\r\n", e.Result); 
       client.Close(); 
       return; 
      } 
      else if (client.State == System.ServiceModel.CommunicationState.Faulted) 
      { 
       textBox.Text += string.Format("Error: {0}\r\n", e.Error.Message); 
      } 
      client.Abort(); 
     } 
    } 
} 

Il n'y a pas de traitement d'exception et de nettoyage ... Je ne sais pas si ce est la manière recommandée, mais je pense qu'appeler abort est la bonne manière. Vous devez gérer les différentes situations d'erreur de toute façon.

4

.Net 4,5

  1. mise en œuvre d'appel Chaque WCF crée un CancellationTokenSource et la stocke dans un endroit accessible à tous les services WCF. C'est à dire. dans les environnements à serveur unique peut être dans un cache en mémoire.
  2. CancellationTokenSource.Token est transmis à tous les appels de méthode initiés par la méthode WCF, y compris les appels aux bases de données et les appels réseau, le cas échéant.
  3. La méthode WCF peut avoir un identifiant unique en tant que paramètre ou peut renvoyer un identifiant unique associé à CancellationTokenSource.
  4. Lorsque le client a besoin d'annuler l'opération, appelle une méthode WCF et transmet l'identifiant unique de l'appel précédent.
  5. CancellationTokenSource est récupérée à l'aide de l'identificateur unique et sa méthode Cancel est appelée. Si le jeton d'annulation est correctement géré, l'opération commencée par l'appel précédent sera bientôt annulée et peut renvoyer une exception OperationCanceledException ou une erreur CancelFault ou une autre erreur indiquant que le client a annulé cet appel.
  6. Lorsque les appels client annulent à l'étape 4 en même temps peut annuler l'appel WCF d'origine. Cependant, si les clients gèrent correctement OperationCanceledException ou CancelFault, cela n'est pas nécessaire. OperationCanceledException peut même atteindre un appel ajax si l'appel d'origine a été démarré à partir d'une page Web.

Quelque chose de similaire peut être implémenté dans des frameworks .Net plus anciens où CancellationToken n'est pas encore disponible.

+2

Merci pour la réponse. Certaines références ou échantillons de code seraient géniaux. – Kurren

10

manière la plus simple que je connaisse le faire avec un client WCF et le motif de async centrés sur les tâches est d'enregistrer une action Abandonner avec le jeton annulation

private async Task CallOrAbortMyServiceAsync(CancellationToken cancellation) 
{ 
    var client = new SomeServiceClient(); 
    cancellation.Register(() => client.Abort()); 
    try 
    { 
     await client.CallMyServiceAsync(); 
    } catch (CommunicationException) { 
      // This will be called when you are cancelled, or some other fault. 
    } 
} 
+2

+1 Belle implémentation! Je recommande juste de disposer le résultat de 'Cancellation.Register()', sinon l'enregistrement pourrait fuir. – BlueStrat

+1

Honnêtement, cela devrait être la réponse acceptée. – Fabien

Questions connexes