Introduction
La dernière fois que nous avons abordé le theme des services WCF, nous avons vu comment mettre en place une solution Client / Serveur à l'aide de WCF. Pour ce faire, nous avions utilisé trois projets, le premier définissant le contrat du service, le second le serveur hébergeant ce service, et le dernier, un client pour le consommer.
Aujourd'hui, nous allons aborder une autre architecture. Admettons que nous avons un programme que nous aimerions piloter depuis plusieurs "front-ends". Une version web, une version en WPF, et une version en silverlight.
La première idée est bien sur de reprendre notre architecture de la dernière fois, mais ceci implique d'avoir deux exécutables pour un seul programme. Un pour le serveur, et un pour l'interface utilisateur, le client. Il s'agit là de la configuration idéale pour minimiser le footprint de notre solution.
Une autre approche possible est d'avoir une application avec la couche business ainsi que l'interface utilisateur dans le même exécutable, mais de quand même pouvoir piloter la couche business depuis un autre "front-end". Deux possibilités ici :
si la première approche ne pose aucun problème majeur, à part le fait qu'il faudra veiller à traiter les appels de la même façon pour les front-ends internes et externes, la seconde approche, elle, pose son lot de soucis.
Avant de commencer, précisons que cette seconde approche n'est pas forcément celle à adopter, mais elle a le mérite de mettre en évidences certains mécanismes de WCF...
Mise en place
Tout d'abord, faisons deux projets, l'un pour le contrat du service, l'autre pour notre application. tout se passe comme lors de notre dernière renncotre avec WCF, à la différence que cette fois-ci, le code instanciant le service et le client se trouvent dans le même projet.
Les sections Service et Client devront donc tous deux se trouver dans le même app.conf, celui se trouvant dans le projet de l'application.
Si tout est correctement fait, le serveur va être instancié correctement, le client également, mais au premier appel d'une operation de notre service, le programme va se bloquer, et après une minute, une exception de TimeOut sera lancée.
Explications
WCF utilise un contexte de synchronisation (SynchronizationContext) pour simplifier la vie du développeur face aux problèmes de multithreading. Par défaut, l'utilisation du SynchronizationContext est activée, mais peut être désactivée en utilisant l'attribut [ServiceBehavior(UseSynchronizationContext = false)] sur la classe qui implémente le service en question. L'utilisation du contexte de synchronization aura pour effet de mettre tous les appels à ce service dans une file d'attente. Les appels dans cette file d'attente seront exécutés dans le thread dans lequel le canal du service a été créé, et ce, dès que celui-ci sera libéré.
Ceci présente l'avantage, si le canal du service est créé dans le UIThread, de pouvoir acceder aux contrôles graphiques sans avoir à synchroniser nous-même des threads afin d'éviter une exception "Cross-Thread operation not valid" : Tous les appels au service seront appellés dans le bon thread.
Malheureusement, dans notre cas précis, le client et le service son exécutés dans le même thread, ainsi, lorsque le client fait appel à une opération du service, l'appel est bloquant, attendant la réponse du service. Le service, quant-à-lui attend que le thread sur lequel il est mis en attente se libère. Le client attend donc la réponse du service, et le service attend que le client libère le thread sur lequel il doit s'exécuter, d'où interbloquage.
Résolution
Pour résoudre ce problème, il suffira de désactiver l'utilisation du contexte de synchronisation avec l'attribut précité, mais par la même, nous perdrons l'avantage qu'il confère, ce qui signifie qu'îl ne nous est pas assuré que les opérations du service seront exécutées sur le "bon" thread.
Ce problème pourra toujours être résolu au cas par cas en assurant manuellement la synchronisation des threads grâce à la méthode Invoke. Il sera alors possible de modifier les éléments de l'interface graphique.
4c7b8782-fc9c-4851-a76d-14af4d4e05e4|0|.0