2009-07-09 6 views
2

Nous avons une méthode qui, en raison du threading dans l'application cliente, nécessite l'utilisation de SynchronizationContext.SynchronizationContext.Post (...) dans le gestionnaire d'événements de transport

Il y a un peu de code que l'un de mes collègues a écrit qui ne me «sent» pas bien, et un profileur de performance me dit que beaucoup de traitement est utilisé dans ce morceau de code.

void transportHelper_SubscriptionMessageReceived(object sender, SubscriptionMessageEventArgs e) 
     { 
      if (SynchronizationContext.Current != synchronizationContext) 
      { 
       synchronizationContext.Post(delegate 
        { 
         transportHelper_SubscriptionMessageReceived(sender, e); 
        }, null); 

       return; 
      } 
    [code removed....] 
} 

Ce juste ne se sent pas droit à moi, que nous affichons essentiellement la même demande à la file d'attente de fil de IUG ... cependant, je ne vois pas anyhting oviously problématique soit, autre que la performance de cette zone de code.

Cette méthode est un gestionnaire d'événements attaché à un événement déclenché par notre assistant de couche de messagerie de niveau intermédiaire (transportHelper) et il existe dans un service qui gère les requêtes provenant de l'interface graphique. Est-ce que cela semble être un moyen acceptable de s'assurer que nous n'obtenons pas d'erreurs croisées? Sinon, y a-t-il une meilleure solution?

Merci

Répondre

6

Traçons ce qui se passe à l'intérieur de cette méthode, et de voir ce qui nous dit.

  1. La signature de la méthode suit celle des gestionnaires d'événements, et que la question indique, nous pouvons nous attendre à être appelé d'abord dans le contexte d'un certain fil qui est pas le thread d'interface utilisateur. La première chose que fait la méthode consiste à comparer le SynchronizationContext du thread dans lequel il s'exécute avec un SynchronizationContext enregistré dans une variable membre. Nous supposerons que le contexte enregistré est celui du thread de l'interface utilisateur. (Mike Peretz a posté une excellente série d'articles d'introduction à la classe SynchronizationContext sur CodeProject)

  2. La méthode trouvera les contextes non égaux, comme cela est appelé dans un thread différent du thread UI. Le contexte du thread appelant est susceptible d'être nul, où le contexte du thread de l'interface utilisateur est à peu près guarantied pour être défini sur une instance de WindowsFormsSynchronizationContext. Il émettra ensuite un Post() sur le contexte de l'interface utilisateur, lui passant un délégué et ses arguments, et retournera immédiatement. Cela termine tous les traitements sur le thread d'arrière-plan.

  3. L'appel Post() provoque l'appel de la même méthode sur le thread UI. Le suivi de l'implémentation de WindowsFormsSynchronizationContext.Post() révèle que cela est implémenté en mettant en file d'attente un message Windows personnalisé dans la file d'attente des messages du thread de l'interface utilisateur. Les arguments sont passés, mais ne sont pas "marshalés", en ce sens qu'ils ne sont pas copiés ou convertis.

  4. Notre méthode gestionnaire d'événements est maintenant appelée à nouveau, à la suite de l'appel Post(), avec exactement les mêmes arguments. Cette fois-ci, cependant, SynchronizationContext du thread et le contexte enregistré sont une seule et même chose. Le contenu de la clause if est ignoré et la partie [code removed] est exécutée.

Est-ce un bon design? C'est difficile à dire sans connaître le contenu de la partie [code supprimé]. Voici quelques réflexions:

  1. Superficiellement, cela ne semble pas être une conception horrible.Un message est reçu sur un thread d'arrière-plan et transmis au thread d'interface utilisateur pour la présentation. L'appelant revient immédiatement faire d'autres choses, et le destinataire peut continuer la tâche. C'est un peu similaire au modèle Unix fork().

  2. La méthode est récursive, d'une manière unique. Il ne s'appelle pas sur le même fil. Au contraire, il provoque un thread différent pour l'invoquer. Comme avec n'importe quel morceau de code récursif, nous serions concernés par sa condition de terminaison. À la lecture du code, il semble raisonnablement sûr de supposer qu'il sera toujours invoqué récursivement exactement une fois, lorsqu'il est transmis au thread de l'interface utilisateur. Mais c'est un autre problème à connaître. Une conception alternative peut avoir passé une méthode différente à Post(), peut-être une méthode anonyme, et éviter complètement le problème de récursivité.

  3. Il ne semble pas y avoir de raison évidente pour qu'une grande quantité de traitement se produise à l'intérieur de la clause if. La révision de l'implémentation WindowsFormsSynchronizationContext de Post() avec .NET reflector révèle des séquences de code modérément longues, mais rien de trop sophistiqué; Tout se passe dans la RAM, et il ne copie pas de grandes quantités de données. Essentiellement, il prépare simplement les arguments et place un message Windows dans la file d'attente des messages du thread de réception.

  4. Vous devriez revoir ce qui se passe dans la partie [code enlevé] de la méthode. Le code qui touche les contrôles de l'interface utilisateur appartient totalement à celui-ci - il doit s'exécuter dans le thread de l'interface utilisateur. Cependant, s'il y a un code qui ne traite pas de l'interface utilisateur, il peut être préférable de l'exécuter dans le thread de réception. Par exemple, toute analyse syntaxique intensive du processeur serait mieux hébergée dans le thread de réception, où elle n'affecte pas la réactivité de l'interface utilisateur. Vous pouvez simplement déplacer cette partie du code au-dessus de la clause if, et déplacer le code restant vers une méthode séparée - pour s'assurer qu'aucune des parties ne soit exécutée deux fois.

  5. Si le fil de réception et le fil d'interface utilisateur doivent tous deux rester sensibles, par ex. Pour envoyer le message entrant et l'entrée utilisateur, vous devrez peut-être introduire un troisième thread pour traiter les messages avant de les transmettre au thread de l'interface utilisateur.

+0

merci, une grande panne. À la fin, j'ai déplacé ce traitement sur une sous-classe de Stack qui est surveillée à un intervalle spécifié. Le code à l'intérieur de la zone [Code Remeoved] est exécuté pour chaque membre de la pile à l'intervalle spécifié. Je crois que la raison de la dégradation des performances était due au fait que, dans la section [Code supprimé], certains appels ne se trouvaient toujours pas sur le thread de l'interface utilisateur, recourant plusieurs fois avant de pouvoir s'exécuter sur l'interface utilisateur. fil. Le déplacement de ces appels vers MonitoredStack a résolu ce problème. – miguel

Questions connexes