VS2012: Créer plusieurs fichiers liés avec un drag-and-drop entre deux projets

This post is available in english.

Comme de plus en plus de plateformes apparaissent dans l'environnement Microsoft, le multi-targeting est de plus en plus commun.

Le multi-targeting est utilisé lorsque les Portable Libraries ne font pas l'affaire. Cela arrive lorsque vous en avez assez de créer des abstractions pour contourner les classes et méthodes qui ne sont pas dans l'intersection des plateformes que vous avez sélectionnées.

Si vous partez avec du multi-targeting, vous avez plusieurs choix, comme d'utiliser le Project Linker, et de créer plusieurs projets avec les mêmes fichiers sources et de les lier entre plusieurs projets.

Ceci étant, pour le moment, Projet Linker ne supporte pas VS2012, vous êtes donc laissé à vous-même avec la fonctionnalité Add as Link de Visual Studio, qui ne supporte pas les répertoires.

Et bien, dans Visual Studio 2012, il y a une nouvelle fonctionnalité qui permet justement cela :

  • Dans le projet source, sélectionnez les fichiers et répertoires à lier dans l'autre projet
  • Déplacez les vers le nouvel endroit dans l'autre projet, mais ne lâchez pas encore le bouton de la souris
  • Maintenez Ctrl + Shift, puis lâchez le bouton

Voilà ! Vous avez maintenant une réplication des répertoires et fichiers.

Vous remarquerez que les répertoires ne sont pas des liens (les liens de répertoires n'existent pas), mais les fichiers le sont. Après tout, c'est mieux que rien !

Vous remarquerez aussi que si vous faites l'opération à nouveau, avec de nouveaux fichiers et répertoires, seuls les nouveaux éléments seront liés et aucune erreur ne sera levée si des fichiers ou répertoires existent déjà.

Rien de bien complexe et sensationnel, mais c'est définitivement une autre économie de temps !

VS2012: Comment changer l'emplacement physique d'un projet

This post is available in english.

Déplacer physiquement un projet dans Visual Studio a toujours été une opération lourde. Cela est cependant souvent nécessaire si l'on veut renommer le répertoire d'un projet [insérer son langage favori].

Dans les versions précédentes de Visual Studio, il fallait déplacer le projet au nouvel endroit puis soit :

  • Enlever le projet de la solution, puis l'ajouter de nouveau en utilisant le nouveau chemin
  • Modifier le très accueillant format .sln comme un guerrier, pour changer toutes les références.

Cela a toujours été une opération ennuyeuse à faire, et surtout couteuse en temps.

Mais à chaque fois, on perd les paramètres de la solution concernant le projet, comme le projet de démarrage par default, ou les configurations de build. Et si vous avez plusieurs configurations de build, vous comprenez facilement la douleur de restaurer tous ces paramètres correctement.

Fort heureusement, il y a un très petit changement dans VS2012 qui permet de fournir un nouvel emplacement pour un fichier de projet, si celui-ci ne s'est pas charge parce qu’il n'a pas été trouvé.

Pour cela:

  • Fermez votre solution dans VS2012
  • Déplacez votre projet à un nouvel endroit
  • Ouvrez la solution
  • Sélectionnez le projet qui ne s'est pas chargé
  • Dans la fenêtre "Propriétés" il y a maintenant un champ éditable nommé "File path" qui permet de sélectionner le nouvel emplacement du projet
  • Changer le chemin
  • Faites un clic droit sur le projet puis "Reload"

C'est fini !

Petite amélioration, grosse économie de temps lors d'un refactoring de solution !

C# 5.0 Async: Trucs et Astuces, Partie 1

This article is also available in english.

TL;DR: Cet article est une discussion à propos de la manière dont C# 5.0 async capture le contexte d'exécution avant d'exécuter une méthode asynchrone, ce qui permet de facilement rester sur la Thread UI pour accéder aux éléments visuels, mais qui peut être problématique lors de l'exécution de taches demandantes en CPU. En dehors du UI Thread, une méthode async peut sauter de Thread en Thread, cassant tout code qui est dépendant de la thread sur laquelle il s'exécute.

C# 5.0 a introduit uniquement deux fonctionnalités dans le langage (async et caller information) et un correctif pour un piège (un changement de scope pour les variables lifted dans une lambda utilisée dans un foreach)

Le plus gros ajout cela dit, en termes de magie de compilateur, est async. Cela a été couvert de partout, et je vais simplement commenter l'ajout de cette fonctionnalité en soi, et les impacts lorsque elle est utilisée.

Cet article est le premier d'une série de petits articles à propos de trucs et astuces à propos de C# async que je vais tenter de couvrir pour donner un peu plus détails en profondeur, et permettre aux développeurs d'en éviter se problèmes.

 

C# 5.0 Async : Les Objectifs

La programmation asynchrone a été une préoccupation cle au cours des dernières années, à l'origine pour permettre d'avoir des interfaces utilisateurs plus fluides. Les GUI sur Windows utilisent toutes le concept de Thread UI, qui prend la forme d'une pompe a messages qui devrait toujours traiter des messages. Si cette pompe est bloquée, parce qu’un clic de bouton est en train d'exécuter du code bloquant, le GUI gèle.

C'est une très mauvaise expérience utilisateur, et Microsoft comme bien d'autres, ont commencé à suggérer (ou forcer, comme en Silverlight ou WinRT) aux développeurs d'utiliser des API asynchrones.

En prenant la direction de l'asynchrone, en utilisant des techniques existantes comme les évènements à base de message de fin, ou le pattern Begin/End, le plus gros challenge est le maintien d'un état entre chaque appel asynchrone. Cela nécessite que le développeur crée sa propre machine à état pour gérer le passage d'état entre chaque étape de cette "opération" logique asynchrone. C'est du code de plomberie assez complexe, et nécessiter son écriture à chaque fois est consommateur en temps et très source d’erreurs.

Du point de vue du développeur, async en C# essaye de faire en sorte de donner une apparence synchrone a du code asynchrone. L'asynchronisme est un gros problème en C#, et l'équipe de C# a introduit cette fonctionnalité pour aider les développeurs lorsqu'ils ont a s'y confronter.

Un autre objectif d'async en C# est la capacité de faire du Thread Yielding, en permettant de relâcher explicitement le contrôle de l'exécution, permettant ainsi à une Thread d'être utilisée de manière plus efficace, en n'attendant pas après un traitement distant. D'une certaine manière, cela ressemble au concept des Fibres Win32, ou plus généralement au multitâche coopératif.

D'après ce que je peux en comprendre, l'objectif à long terme est que les développeurs favoriseront l'écriture de méthodes asynchrones qui permettront de relâcher la Thread UI lorsqu'il y a un appel asynchrone.

 

C# 5.0 Async, Magie Niveau 1

Lorsque l'on commence à l'utiliser, on a la sensation de faire de la magie, au premier abord. Il suffit juste de placer quelques async et await par-ci, parla, et ça fonctionne.

Le premier niveau de magie se trouve dans la machine à état qui est généré par le compilateur, de la même manière qu'un développeur le ferait. Pour le développeur, tout ceci est presque transparent. Une méthode async est alors une série de delegates chaines qui sont exécutes après chaque utilisation du mot clé await

Il suffit simplement de jeter un œil à ce que ILSpy produit pour voir les entrailles d'une méthode async, ou l'on constate que la méthode est coupée en petits morceaux, déclenches par la présence du mot clé await.

On peut constater que toutes les méthodes async sont placées dans des "Display Class", et plus spécifiquement dans une méthode nommée MoveNext.

Bien que savoir que cette génération de code est présente est plus anecdotique qu'utile, cela aide malgré tout à comprendre le fonctionnement global d'une méthode async.

 

Le SyncronizationContext, Magie Niveau 2

Si vous avez déjà écrit du code asynchrone (sans async), vous devez connaitre les méthodes comme Dispatcher.BeginInvoke ou Form.BeginInvoke, qui permettent d'exécuter du code sur la Thread UI. Ainsi, à moins que le framework ne le fasse pour vous (comme WebClient en Silverlight) vous devez utiliser une de ces méthodes pour mettre à jour votre UI lorsque le travail asynchrone est terminé.

Vous devez alors vous demander... Comment une méthode async permet la mise à jour du UI ? C'est la Magie Niveau 2, connue sous le nom de SynchronizationContext.

Le contexte de synchronisation est très grossièrement une abstraction d'une queue de messages, ou il est possible de mettre en queue de manière synchrone ou asynchrone du travail, puis d'être notifié lorsque c'est terminé. Le Dispatcher de Silverlight et WPF en disposent d'un qui est automatiquement assigne lors de l'exécution de code relie au UI.

Lors de la création d'une méthode async, le compilateur s'appuie sur AsyncTaskMethodBuilder. C'est une classe de la BCL qui est utilisée comme un coordinateur de la magie qui fait en sorte que lorsqu'une méthode async est créée, le contexte de synchronisation courant de l'appelant est capturé. Par la suite, ce contexte est réutilisé pour mettre en queue du code de la méthode async a exécuter après le premier await, de manière a ce qu'il reste dans le même contexte.

Bien que ce soit à l'interne un peu complexe, c'est très efficace lorsque l'on travaille avec la Thread UI dans une méthode async. Il n'y a alors presque plus besoin de se préoccuper de revenir sur la Thread UI pour la mettre à jour, puisque le SynchronizationContext a ramène automatiquement le flot d'exécution dessus.

 

Asynchronisme vs. Concurrence

Maintenant que le code est exécuté dans une méthode async, on pourrait être tenté de penser que peu importe ce que le code va faire, le UI ne va pas "geler", puisque l'on est dans une méthode async.

On peut alors être tenté de faire un appel web, déserialiser son contenu dans la méthode async. On constate alors très rapidement que le UI gèle malgré tout lorsque l'on déserialize un gros document JSON/XML.

Il y a une chose à se rappeler avec l'asynchronisme. Ce n'est pas de la concurrence.

La partie compliquée ou le SynchronizationContext ramène sur le UI Thread, cela ramène vraiment dessus. La déserialization de JSON est une opération qui implique principalement le CPU, ce qui va faire en sorte que la Thread  courante va être bloquée, nous ramenant au point de départ: Le UI gèle ou "rame".

A ce moment, on veut de la concurrence, là ou async n’aide pas beaucoup. Il est nécessaire d'exécuter du code dans une autre Thread, soit par Thread, Task ou le ThreadPool, ... Là où il est nécessaire de protéger (lock) les ressources correctement, la gestion correcte de l'abandon, et tous ces autres petits détails piquants.

A partir du moment où l'on sort du contexte d'une méthode async attachée au UI, il est nécessaire de communiquer avec le UI manuellement, principalement en utilisant Dispatcher.BeginInvoke et similaires.

Pour ma part, je pense que ce sera (et est déjà) un des plus gros malentendus de l'utilisation de async, ou les développeurs vont penser qu'ils vont faire du travail parallèle en utilisant async, malgré que ce ne soit pas le cas.

 

Cycle de Vie des Méthodes Async

Si l'on considère ce code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
private async void Button_Click_1(object sender, RoutedEventArgs e)

{

await Test();



var t = Task.Run(async () => await Test());



await t;

}



private async Task Test()

{

Debug.WriteLine("SyncContext : {0}", SynchronizationContext.Current);



Debug.WriteLine("1. {0}", Thread.CurrentThread.ManagedThreadId);



await Task.Delay(1000);



Debug.WriteLine("2. {0}", Thread.CurrentThread.ManagedThreadId);



await Task.Delay(1000);



Debug.WriteLine("3. {0}", Thread.CurrentThread.ManagedThreadId);



await Task.Delay(1000);



Debug.WriteLine("4. {0}", Thread.CurrentThread.ManagedThreadId);

}

 

Qui, exécuté produit ceci :

 

SyncContext : System.Windows.Threading.DispatcherSynchronizationContext

1. 10

2. 10

3. 10

4. 10

SyncContext :

1. 6

2. 12

3. 12

4. 6

      

Il faut remarquer que la première exécution de la méthode async reste sur la même Thread, en utilisant le DispatcherSynchronizationContext. C'est la raison pour laquelle il est possible d'exécuter du code qui modifie le UI dans cette méthode.

Dans le cas de WPF/Silverlight/Metro, le DispatcherSynchronizationContext est automatiquement assigné dans une variable du TLS (l'attribut ThreadStatic), ou chacun des callbacks UI y aura accès, et par extension, toute méthode async exécutée dans ce contexte.

Mais lorsque cette même méthode est exécutée a l'intérieur d'une Thread démarrée manuellement, son comportement est complètement diffèrent. Le code démarre sur la Thread 6, continue sur la Thread 12, puis à nouveau sur 6.

La raison de ceci est l'absence de contexte de synchronisation lorsque la méthode async a été démarrée. Le comportement par défaut d'une méthode async, qui est gérée par AsyncTaskMethodBuilder, est de mettre en queue du travail sur le ThreadPool. Par nature, il n'est pas possible de choisir quelle Thread sera utilisée pour exécuter du travail mis en queue, ce qui provoque ces changement de thread.

Il ne faut donc pas prendre pour acquis qu'il est possible d'exécuter du code UI dans une méthode async, et qu'il est plutôt dangereux de s'appuyer dessus.

 

Migrer du Code Dépendant du Threading Context

Si l'on considère ce vieux code, qui pouvait bien s'exécuter en WinForms 2.0 :

1
2
3
4
5
6
7
8
9
private void Button_Click_1(object sender, RoutedEventArgs e)

{

_lock.AcquireWriterLock(1000);



// A context sensitive operation

Thread.Sleep(3000);



_lock.ReleaseWriterLock();

}
 

 

Ce n'est pas le genre de code que l'on veut voir derrière un gestionnaire d'évènement UI, mais l'utilisation d'un ReaderWriterLock peut définitivement se trouver dans un service d'arrière plan, donc continuons.

Considérons que nous devons porter ce code à WinRT, et au lieu d'appeler Thread.Sleep, il faut appeler une méthode async qui va faire des accès aux fichiers :

1
2
3
4
5
6
7
8
9 

  private async void Button_Click_2(object sender, RoutedEventArgs e)

{

_lock.AcquireWriterLock(1000);



// Do some context sensitive stuff...

await Task.Delay(1000);



_lock.ReleaseWriterLock();

}

Tout va s'exécuter correctement.

Mais on remarque que le code gèle le UI, alors on le place dans une Thread d'arrière-plan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void Button_Click_3(object sender, RoutedEventArgs e)

{

Task.Run(

async () =>

{

try

{

_lock.AcquireWriterLock(1000);



// Do some context sensitive stuff...

await Task.Delay(1000);



_lock.ReleaseWriterLock();

}

catch (Exception ex)

{

Debug.WriteLine(ex);

}

}

);

}

 

(Note: Nous reviendrons sur les lambda async, alors continuons :) )

Ce code va très certainement ne pas s'exécuter, probablement neuf fois sur dix.

La raison est que le code s'exécute sur une thread d'arrière-plan, et que le code après le premier await va très probablement ne pas s'exécuter sur la même thread que le code avant le await.

On pourrait avoir de la chance et que le code s'exécute sur la même Thread. Le code pourrait marcher. Des fois.

C'est la raison pour laquelle il n'est pas autorisé d'utiliser le mot clé lock dans une méthode async, puisque le compilateur ne peut pas s'assurer du fait que le code va être exécuté sur la même thread, puisque cela dépend de l'appelant de la méthode.

 

A Retenir

  • Jeter un œil au code généré derrière une méthode async, c'est très instructif.
  • Une méthode async n'est pas garantie de rouler sur la Thread UI, elle utilise le SynchronizationContext ambiant.
  • Attention au code dépendant de la thread en cour dans une méthode async, puisque la Thread courante pourrait changer au cours de l'exécution de la méthode, dépendant du SynchronizationContext ambiant.

A bientôt pour la suite de cette petite série !

 

Améliorer le Démarrage des Applications XAML Style Metro avec le JIT Multicœurs

An English version of this post is available.

TL;DR: Avec Windows 8 Release Preview, Microsoft a introduit le JIT Multi-cœurs, qui permet d'enregistrer la signature des méthodes compilées par le JIT durant le démarrage d'une application. Cet enregistrement peut être intégré au package d'une application pour obtenir un démarrage plus rapide sur des processeurs multi-cœurs. L'amélioration peut aller de 20% à 50%.

 

Depuis le début de l'année, j'ai eu la chance de travailler avec des personnes très intéressantes de chez Microsoft, et une fonctionnalité qui est ressortie est l'utilisation du nouveau JIT Multi-cœurs pour les applications Style Metro.

 

Le Compilateur JIT

L'étape de compilation JIT est présente dans .NET depuis la version 1.0. Elle prend du Intermediate Language (MSIL) que le compilateur C# génère, puis le compile en instructions natives pour le CPU.

Cette étape supplémentaire de génération d'IL a prouvé sont intérêt a de nombreuses occasions, et parce que l'IL est indépendant de la plateforme, cela permet de ne le compiler qu’une fois pour l'exécuter sur plusieurs types de CPUs.

Cela a donc une fois de plus été très intéressant d'avoir cela avec l'introduction de WoA (ou Windows RT), ou il est très facile de produire un package d'application qui est compatible avec les trois plateformes supportées par les applications Metro (x86, x64 et ARM).

Mais cela vient aussi avec ses désavantages. C'est une étape supplémentaire avant d'exécuter le code, ce qui veut dire que cela va ralentir l'exécution du programme, tout particulièrement au démarrage.

Cela dit, il est toujours possible d'utiliser NGEN pour précompiler tout le code à l'avance. Mais NGEN est plutôt compliqué à utiliser, et il requiert que toutes les assemblies soient places dans le GAC. Cela a le désagréable effet de faire en sorte que NGEN est assez peu utilisé, excepté pour les librairies.

 

Le JIT et les Sauts de Performance du Matériel

En l'an 2000, les ordinateurs n'étaient pas très rapides, et prendre la route du JITing,  avec les technologies disponibles à l'époque, nécessitaient l'utilisation de NGEN pour obtenir une performance décente.

Puis avec le temps qui a passé, les ordinateurs sont devenus de plus en plus rapides, et le JIT a eu tendance à devenir transparent. De nos jours, il peut pratiquement être ignoré, a moins d'avoir une montagne de code a exécuter.

Mais comme l'histoire a tendance à se répéter, et puisque nous sommes en train de courir après l'autonomie des batteries, des devices comme Windows Phone ou les tablettes WoA qui utilisent des CPU peu puissants commencent à apparaitre. Comme ils ont généralement une performance similaire a ce qui pouvait exister en 2000, le JIT redevient à nouveau un problème que j'ai eu à découvrir sous Windows Phone.

Je m’attends à voir le même type de problèmes de performances lies au JIT sur WoA, alors que pendant ce temps, nous sommes tous en train de jouer avec des i5 ou i7 super-rapides qui nous cachent l'impact du JIT.

 

Le JIT Multicoeur (MCJ)

Microsoft semble avoir pris le taureau par les cornes, et a attaqué le problème du JIT en utilisant les technologies récentes.

Historiquement, le JIT se faisait sur la Thread qui appelle la méthode a JITter, impactant de ce fait la performance cette méthode. De nos jours, la plupart des CPUs on plusieurs cœurs, même sur ARM, et permettre au JIT de les utiliser doit certainement être un gros avantage pour la performance.

Dans .NET 4.5, cette fonctionnalité permet d'enregistrer des "Profils" où la signature des méthodes JITées sont persistées dans un fichier. Cela permet, lors des démarrages ultérieurs de l'app, de pré-JITter les méthodes à l'avance sur plusieurs cœurs, de manière a ce que le chemin d'exécution principal n'ai pas besoin d'attendre que le JIT fasse son travail.

Cette fonctionnalité ne semble pas être base sur NGEN, ce qui veut dire qu'il n'est pas nécessaire de signer fortement les assemblies, ni de les mettre dans le GAC. Une grande avancée, si vous voulez mon avis.

 

Le JIT Multicœurs et les Applications Style Metro

Le Profil .NET Core utilise pour exécuter les applications XAML/C# est très probablement basé sur une bonne partie de .NET 4.5 complet, et cette fonctionnalité y est aussi disponible. Elle est spécifiquement faite pour les premiers démarrage des applications, puisqu'après 24 heures, l'application est totalement JITée en image native. A ce moment, le JIT Multicœurs n'est plus utilisé.

Il existe une API pour générer ces profils en .NET 4.5 complet, mais la technique pour les applications Style Metro est un peu cachée, mais très simple.

Microsoft a publié récemment un article nomme KB2715214 “How to reduce JIT time during initial start of Windows Metro style apps sur la manière d'utiliser cette fonctionnalité.

En voici une version raccourcie :

  • Dans l'assemly principale, créez un fichier vide nomme “[assembly_name]_[EntryPoint].profile”. Le "Entry Point" est généralement "App". Assurez-vous que le fichier est vide; il doit montrer 0 Ko dans l'explorateur de fichiers.
  • Lancez votre application en mode Release sans le Debugger, et essayez de naviguer le plus possible dans votre application, de manière à capturer le plus de méthodes possible. Le profiler prends 10 secondes de données.
  • Naviguez vers %localappdata%\Packages\[YOUR_APP_PACKAGE]\AC
  • Récupérez le nouveau fichier de profil et remplacez celui de votre projet.

C'est terminé !

Si le fichier n'est pas généré, cela peut être parce que :

  • Vous ne l'avez pas nommé correctement,
  • Vous n'avez pas mis le mode "Content" dans ses propriétés,
  • Vous exécutez le programme sur une machine avec un seul cœur.

 

Mesures sur des Applications Publiées

J'ai travaillé sur 3 applications Metro (Photobucket, Allrecipes et TuneIn) qui ont été publiées sur le Store de Windows 8 Release Preview, et l'utilisation de cette fonctionnalité a permis d'améliorer sensiblement leur temps de démarrage. Les améliorations vont de 25% à 50%, dépendant du volume de code exécuté durant le démarrage de l'application.

Dans la pratique, sur un Core Duo Mobile (1.8 GHz), l'application prend 3.2s à démarrer sans MCJ, et a peu près 1.73s avec MCJ. Sur un Quad Core i7 2.8Ghz, l'application démarre en bien moins qu'une seconde, ce qui rend la mesure particulièrement peu fiable, surtout lorsque l'on considère la performance ressentie.

Je suis très impatient de savoir ce qui va se passer sur des tablettes WoA... Enfin, on le saura à l'automne.

 

Désavantages du MCJ

Bien que cette fonctionnalité est très intéressante, elle enregistre un démarrage de l'application. Cela veut dire que dès que le code change, le MCJ va se mettre a JITter des méthodes qui ne sont potentiellement plus dans le chemin de démarrage.

Cela ne va pas déstabiliser l'application, mais elle va perdre progressivement les bénéfices du MCJ, et dans le pire des cas, dégrader le temps de démarrage de l'application. Mais c'est très peu probable.

Vous devez alors penser à recréer les profils de JIT juste avant de publier votre application, et ne pas s'appuyer sur des profils générés précédemment.

 

Pas de Threads pour vous ! (dans les applications style Metro)

This article is available in english .

Comme dirait ce cuisinier, puisque vous avez probablement utilisé les threads de la mauvaise manière (comme Microsoft semble le penser), vous ne pourrez plus utiliser la classe Thread dans les applications Metro sous Windows 8. La classe n'est tout simplement plus la, tout comme Timer ou ThreadPool.

Cela pourrait vous choquer, mais cela fait en fait beaucoup de sens. Mais ne vous inquiétez pas, le concept d'exécution parallèle est toujours présent, il prend simplement la forme de tâches.

 

Pourquoi utiliser les Threads n'est pas bon pour vous

Les threads sont un outil très puissant, mais il y a un certain nombre de problèmes qui viennent avec :

  • Les exceptions non gérées dans les Threads, que ce soit depuis un Timer, une Thread ou une Thread du ThreadPool, mênent à la fin du processus
  • Utiliser Abort est plutot mauvais pour le process, et devrait être évité
  • Les développeurs ont tendance à utiliser Thread.Sleep pour attendre arbitrairement pour un temps défini constant qui sera très probablement incorrect, et qui va utiliser inutilement des cycles CPU pour gérer ces Threads qui ne font rien,
  • Les développeurs ont tendance à créer des systèmes complexes pour chainer des opérations entre threads, qui la plupart du temps échouent lamentablement.

Il existe d'autres scénarios à éviter, mais ce sont les principaux pour lesquels les Threads sont à éviter.

Cela fait des années que je recommende de rester le plus loin possible des Threads, au moins en ne les utilisant pas directement, pour toutes ces raisons.

 

Utiliser Task, exclusivement

Puisque Microsoft a pris le temps de repenser certains concepts qui avaient été introduits dans la BCL et le CLR original en 1999, ils ont probablement pensé qu'il était temps d'enlever la classe Thread à la faveur de la classe Task, qui rend des services bien plus efficaces et qui gère efficacement les cas que j'ai listé plus haut :

Toutes ces opérations se mélangent très bien avec la nouvelle fonctionnalité async de C#5, avec laquelle il est très simple d'attendre une Task.

 

ThreadPool et Timer déplacés vers WinRT

Le ThreadPool est en fait toujours la, et il en est de même pour Timer sous la forme de TheadPoolTimer, mais tous deux ont été déplacées vers WinRT.

ThreadPool est maintenant async awaitable, supporte les priorités, a l'instar de Task. Pour le moment, je ne vois pas de bonne raison de l'utiliser, puisque Task propose bien plus de fonctionnalités.

ThreadPoolTimer peut être intéressant, bien que les exceptions lancées dans ce contexte semblent être gérées de manière silencieuse par WinRT. Il me manque encore à trouver ou ces exceptions vont... Ceci étant, je recommende de ne pas l'utiliser, à la faveur de Observable.Timer des Reactive Extensions qui sont bien plus utiles que ce simple Timer.

 

Restes de Threads

Il reste cependant un endroit ou les Threads font encore surface dans la BCL.

Si l'on prend ce code C# :

public IEnumerable IteratorSample() 
{ 
    yield return 1; 
}

Une fonctionnalité que les iterators générés par le compilateur est qu'ils ne sont pas Thread Safe et doivent être utilisés sur la thread sur laquelle ils ont été créés. L'iterator capture alors la Thread originale, et vérifie que les appels suivants seront bien sur la même thread.

Si l'on regarde donc vers quoi le code est étendu à l'interne, en utilisant un compilateur C# sur .NET 4.0 et antérieur:

[DebuggerHidden] 
public d__0(int <>1__state) 
{ 
   this.<>1__state = <>1__state; 
   this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; 
}

Alors qu'avec .NET 4.5, ceci va être généré:

[DebuggerHidden] 
public d__0(int <>1__state) 
{ 
   this.<>1__state = <>1__state; 
   this.<>l__initialThreadId = Environment.CurrentManagedThreadId; 
}

Le code généré pour .NET 4.5 fait alors usage de la nouvelle propriété Environment.CurrentManagedThreadId, puisque les iterateurs ont malgré tout besoin du vrai Thread ID, même si la classe Thread n'existe plus.

Intéressant, n'est-ce pas ?

Cela a un désagréable effet de bord, cela dit. Du code C# compilé par un compilateur en dessous de .NET 4.5 n'est plus binairement compatible avec la BCL des applications style Metro. Mais ce n'est finalement pas gênant, puisque la plupart de la surface des API de .NET ont soit changé (pour être async seulement), été déplacées (comme l'API de reflection) ou bien ont tout simplement disparues (comme System.IO qui s'est déplacé dans WinRT). Vous aurez donc à adapter votre code, de toute façon.

Je suppose que l'équipe de C# a du se battre pour cette fonctionnalité afin de garder la compatibilité, et garder le même comportement que dans les précédentes version de C#. Et je suis content que cette propriété soit toujours la, puisque je l'utilise dans mon framework de logging.

Happy WinRT'ing !

[WPDev] Trucs et astuces sur la mise à jour Tiles dans Windows Phone Mango

This article is also available in english.

Dans les dernières applications Windows Phone publiées sur lesquelles j'ai travaillé, comme Foursquare, Flickr ou TuneIn (et d'autres sont à venir), toutes ont des Live Tiles, à la fois en mode Pull et générées localement. Mais il y a quelques petites choses à savoir pour les utiliser efficacement, et vous allez les découvrir en lisant cet article.

Les Live Tiles sont fonctionnalité très puissante, laissant l'utilisateur personnaliser l'écran d'acceuil à sa manière, sans personne ne le forçant à avoir un Tile qu'il ne veut pas. C'est le même raisonnement derrière l'absence d'API pour ajouter des éléments dans la barre des tâches de Windows 7.

Live Tiles en mode Pull

Dans l'application Foursquare, il y a le Tile principal qui est mis à jour via le modèle Pull, à peu près toutes les heures (et le "a peu près" a une signification très forte).

Ce Tile qui affiche le tableau des scores est construit dans un service Azure en utilisant un rendu WPF offscreen, sur la base des demandes du moteur de Tiles. Cet Tile a été construit de cette manière en raison des capacités limitées de Nodo, où les Background Agents n'étaient pas disponibles pour la génération locale d'images.

Avec Windows Phone NoDo, de nombreux utilisateurs se plaignaient que le Tile ne se mettait pas à jour, et très franchement, cela a été un mystère jusqu'à la fin. Il se trouvait alors que les Tiles étaient mise à jour sur certains appareils, mais pas sur d'autres, et uniquement si la batterie était chargée à plus de 50%.

De plus, ces Tiles ne se mette pas à jour si l'appareil est en veille, seulement quand l'utilisateur visualise l'écran d'accueil, et lorsque le delai minimal de mise à jour a expiré. Je dis «semble», car il semble que les règles derrière cette mise à jour de Tiles n'étaient pas claires, voire buggées.

Cela a bien changé avec Mango. Les Tiles en mode Pull sont mises à jour presque tout le temps, car la règle de la batterie à plus de 50% semble toujours appliquée.

Il y a aussi la règle des taille de fichier 80Ko à ne pas dépasser, sinon votre Tile ne sera pas affiché.

Live Tiles mis à jour par code

Dans Foursquare, l'utilisateur peut choisir d'épingler un Live Tile d'un endroit connu à son écran principal pour y accéder facilement.

La mise à jour de ces Tiles peut être effectuée avec l'API ShellTile, et avec laquelle il est possible de définir quatre éléments:

  • Un titre pour les faces avant et arrière
  • Une URL de l'image sur les faces avant et arrière
  • Un texte de quatre lignes sur le verso
  • Un nombre sur la face avant (pour afficher compteur de notifications, par exemple)
  • Et vous pouvez oublier les Tiles animées comme le Hub des personnes

Bien que toutes ces fonctionnalités soient intéressantes, une seule d'entre elles est en fait très utile: les URL d'images.

Toutes les autres propriétés ne sont pas stylable, ne suivent que les couleurs du système, et ne se prêtent pas très bien à etre utilisées avec du contenu généré par l'utilisateur. Dans le cas de Foursquare, Flickr et TuneIn, les images affichées sont du contenu fourni par les utilisateurs, et avoir un text blanc sur un fond blanc n'est pas très utile.

Au sujet des URL d'images, il est possible de donner une URL externe pour l'image de la Tile, mais le contenu est visible tant que le téléphone ne redémarre pas. Si l'appareil est redémarré, le Tile perd son contenu. Un comportement assez étrange, selon moi.

Utiliser le nouveau schéma d'URI isostore

Heureusement, il est désormais possible de stocker l'image localement dans un dossier spécial dans le répertoire Isolated Storage nommé /Shared/ShellContent, et d'utiliser le nouveau préfixe URI "isostore".

Cela signifie que vous pouvez télécharger l'image à afficher puis la stocker dans l'isolated storage, et l'utiliser à partir de là comme ceci : "isostore:/Shared/ShellContent/MyTile.jpg".

Mais il y a un gros problème en utilisant de cette technique: il n'est pas possible de limiter la taille de l'image téléchargée. Donc, si le fichier est plus gros que 80Ko, vous êtes coincé avec la couleur du thème de l'utilisateur.

D'ailleurs, je serais curieux de connaître l'histoire derrière le préfixe isostore, car il n'y a que deux endroits où il est possible de l'utiliser, les bases de données SQL CE et Live Tiles. Ce préfixe ne peut pas être utilisé sur la propriété Source pour les contrôles Image, même si il serait très utile. Mais je m'égare.

Génération Live Tiles

Heureusement, il est tout à fait possible de générer localement le contenu d'une Tile en entier, en utilisant la méthode WriteableBitmap.Render. Cette méthode permet le rendu hors-écran de n'importe quel UIElement, puis de le persister en utilisant la méthode d'extension SaveJpeg.

Les Tiles pour Foursquare, Flickr et TuneIn sont générés de cette manière, en utilisant un contrôle utilisateur qu'un vrai Designer (oui, oui) a créé. Cela donne de beaux Tiles, et la mise en page comme le style peuvent être mis à jour en fonction de contenu dynamique.

Voici quelques trucs pour générer des Tiles:

  • Le «nouveau» (presque) contrôle Silverlight 4 ViewBox est très pratique pour redimensionner le texte pour s'adapter la taille 173x173,
  • Vous pouvez utiliser un contrôle Image dans votre source de rendu, mais vous devez attendre que le BitmapImage (et non pas l'image) lève l'événement ImageLoaded, (Les Reactive Extensions peuvent être très pratiques pour ça)
  • Vous aurez également besoin d'appliquer CreateOptions à None sur votre BitmapImage pour vous assurer que l'image est téléchargée immédiatement,
  • Si vous téléchargez des images, assurez vous d'avoir une image locale par défaut en dessous, au cas où l'image distante ne puisse pas être téléchargée,
  • Avant de rendre le contenu, assurez-vous d'appeler les méthodes Measure et Arrange pour forcer la mise à la taille de 173x173 requise pour les Tiles.
  • Vous pourriez avoir besoin d'appeler Measure et Arrange plusieurs fois, puisque pour une raison obscure, le contrôle devant être rendu peuvent ignorer un temps ces deux commandes. Vérifiez les valeurs des propriétés ActualHeight et ActualWidth pour voir si elles sont correctes.
  • Assurez-vous de rendre votre Tile avant de le placer sur l'écran d'accueil ! L'application est complètement stoppée lorsque vous appelez la commande de Pinning, et l'utilisateur peut ne pas revenir à votre application et vous permettre de finir le rendu d'image.
  • Ne prenez pas trop de temps à rendre votre Tile, parceque si vous attendez trop, l'expérience utilisateur est assez mauvaise. Cela peut certainement être un défi lorsque du téléchargement de contenu est à afficher dans le Tile.

Mais si vous utilisez cette technique, vous ne pouvez rafraîchir vos Tiles que lorsque l'application est exécutée, sauf si vous utilisez la nouvelle fonctionnalité Background Agents de Mango.

Mise à jour des Tiles avec des Background Agents

Les Background Agents sont la méthode proposée par Microsoft pour laisser des applications tierces exécuter du code en arrière plan, mais avec quelques restrictions importantes, comme la mémoire (4 Mo), l'intervalle d'exécution (30 minutes) ou les limites de la durée (15 secondes) pour des tâches périodiques.

Voici quelques astuces à propos des Background Agents:

  • Les agents périodiques s'exécutent à un intervalle de 30 minutes, non configurable. Alors soyez gentils, vous pouvez ajouter de la logique pour éviter de mettre à jour les Tiles trop souvent, comme par exemple ne pas rafraîchir pendant la nuit, et mettre à jour le Tile toutes les 3 à 6 heures.
  • N'attendez pas trop pour générer les Tiles, ces 15 secondes passent très vite. Votre tâche peut se aussi se faire terminer par le système d'exploitation avant cette durée.
  • Ne vous reposez pas uniquement sur l'exécution de l'agent pour mettre à jour vos Tiles, l'utilisateur peut désactiver votre agent en utilisant les Paramètres / Applications / Tâches en arrière plan. Aussi, le système d'exploitation peut l'empêcher de s'exécuter, si cela devient nécessaire.
  • Abusez de la fonction ScheduledActionService LaunchForTest pour tester votre agent,
  • Un agent exécute votre code dans un processus différent de celui de votre application, ce qui signifie que votre application et l'agent peuvent s'exécuter en même temps. Prêtez attention aux des ressources partagées, comme les bases de données ou les fichiers du Isolated Storage.
  • Si vous mettez vos Tiles à jour à la fois dans votre application et votre agent, vous pouvez avoir besoin d'ajouter quelques IPC en utilisant un bon vieux Mutex nommé (ahh, le bon vieux temps) et de synchroniser l'accès à vos ressources.
  • Evitez de référencer trop d'assemblies dans votre agent, il y a beaucoup d'API non prises en charge qui peuvent faire échouer la certification de votre application au Marketplace .
Vous pouvez pre-valider vous même votre application en utilisant les tests automatisés du kit de test du Marketplace.

A propos du premier point, si je comprends très bien les inquiétudes sur la consommation d'énergie pour les tâches executées à moins de 30 minutes, je ne comprends toujours pas pourquoi cet intervalle ne peut être réglé plus grand, pour éviter ce même problème de consommation d'energie. Il doit aussi être une histoire derrière tout ca...

Enfin sur le dernier point, pendant la phase bêta du SDK de Mango, la classe StandardTileData a été considérée comme une API non prise en charge, ce qui rendait la mise à jour automatique des Tiles en tâche de fond impossible. Cela a changé depuis la RC du SDK et il est maintenant possible de mettre à jour les Tiles par des agents.

Amusez-vous biens avec les Tiles!

Team Build et Windows Phone 7

This post is available in english.

Construire des applications pour Windows Phone 7 de manière agile encourage l'utilisation de l'Intégration Continue, et cela peut être fait avec Team System 2010.

Il y a cependant quelques éceuils à éviter pour y arriver, mais une fois évités cela donne de bons résultats !

Je ne couvirais pas toutes les bonnes choses des builds automatisés, cela a déja été couvert largement.

 

Ajouter des Tests Unitaires

Avec l'intégration continue pour faire des builds qui terminent avec succès après chaque "Check-in" de code source, il y a aussi l'exécution automatisée de tests unitaires. Team System donne la possibilité de connaitre la couverture de code des tests unitaires qui ont été executés, et d'en faire des rapports avec Reporting Services, ou des statistiques peuvent être consultées et qui donnent quelques bons indicateurs de la santé du projet.

Cela dit, malheureusement pour nous, il n'existe pas pour le moment de moyen pour automatiser l'exécution de tests en utilisant le runtime de Windows Phone 7. Mais si une bonne approche MVVM est utilisée, les View Models peuvent etre compilés sur plusieurs plateformes, puisqu'ils ne dépendent pas de composants spécifiques à la plateforme WP7. De cette manière, il est toujours possible de tester le code en utilisant le runtime de .NET 4.0 avec MSTest et les Projets de test de visual Studio 2010.

Pour éviter de répeter le code, des fichiers "Multi-targeted" peuvent être intégrés dans plusieurs projets qui ne visent qu'une seule plateforme soit en :

  • Utilisant l'outil Project Linker, en liant plusieurs projets,
  • En créant plusieurs fichier de projet dans le même répertoire et en utilisant le menu "Include File" dans le "Solution Explorer". Il faut aussi s'assurer de changer le nom de sortie de l'assembly pour quelque chose comme "MyAssembly.Phone.dll" pour éviter les conflits.

Les fichiers "Multi-Targeted" peuvent utiliser la directive #if, et le define WINDOWS_PHONE ou bien son absence, pour compiler pour la bonne plateforme.

Il est existe aussi d'autres options comme la création de projets avec l'add-in Portable Library, mais il y a quelques problèmes d'intégration et contraintes en utilisant cette méthode. Vous auriez probablement besoin d'externaliser du code qui n'est pas supporté.

Tester avec MSTest permet de s'assurer que le code s'execute correctement sur le runtime .NET 4.0, mais ne s'assure pas de son bon fonctionnement sur le runtime WP7. Pour en être certain, et puisque Windows Phone 7.0 est construit sur Silverlight 3, des outils comme le Unit Test framework pour SL3 peuvent être utilisés pour executer manuellement les test dans l'émulateur.

Cela ne peut pas être intégré dans le processus de build pour l'instant. Cette étape devra donc être intégrée dans votre processus de QA.

TeamBuild avec le Toolkit WP7

Pour pouvoir compiler des applications WP7 sur une machine de build, il faut installer le SDK puis un controlleur et/ou agent de Team Build.

Créer une définition de build se fait de la même manière que pour des builds normaux, à l'exception d'un seul détail. Vous devez configurer le paramètre MSBuild platform à x86 au lieu de Auto si la machine de build est un Windows 64 Bits. Cela force MSBuild à utiliser le runtime 32 bits et non 64 bits ou le SDK ne fonctionne pas proprement.

Si vous le ne faites pas, lorsque le projet sera compilé, vous aurez d'étranges messages comme celui ci :

Could not load file or assembly 'System.Windows, Version=2.0.5.0'

Qui est vraiment étrange puisque le SDK est installé, et que ce fichier est bel et bien disponible.

Vous pourrez aussi voir que si vous installez cette DLL du SDK dans le GAC, vous aurez ce beau message : 

Common Language Runtime detected an invalid program.

Message très commun lorsque l'on mixe des assemblies 32 et bits, en particulier lorsque l'architecture a été spécifiée explicitement, au lieu de "Any CPU". N'installez donc pas cette DLL dans le GAC et assignez l'architecture à x86.

C'est tout pour cette fois, et bon WP7 building !

[WP7] HttpWebRequest et le problème d'écran noir de l'application Flickr

TL;DR: Lorsque je travaillais à corriger le problème de "L'écran noir" au démarrage de l'application Flickr 1.3 pour Windows Phone 7, j'ai constaté que HttpWebRequest fait à l'interne à l'interne un appel synchrone à la thread UI ce qui peut avoir un impact particulierement négatif sur l'expérience utilisateur. La totalité de la construction d'une requête asynchrone est faite sur la thread UI, et ce n'est pas possible de le changer.

Lorsque l'on programme pour Windows Phone 7, on entend souvent que pour améliorer la performance perçue, il faut sortir de la Thread UI (aussi nommé le dispatcher) pour effectuer des opérations qui ne sont pas liées à l'interface utilsateur. Par bonne perfomance perçue, j'entend par la que l'interface répond immédiatement, et ne s'arrête pas lorsque des opérations d'arrière plan sont en cours d'exécution.

Pour faire cela, il faut utiliser des techniques connues comme l'ajout dans le ThreadPool, créer une nouvelle Thread, ou bien utiliser le pattern Begin/End.

Tout ceci est tout à fait vrai, et un très bon exemple d'une mauvaise utilisation de la Thread UI est le traitement de la réponse d'une requête web, particulièrement en utilisant WebClient où les évènements sont exécutés dans le contexte du dispatcher. Du point de vue d'un novice, ne pas avoir à s'occuper des changements de contextes pour le développement d'une application simple qui met à jour l'écran, cela donne une très bonne expérience de développement.

Mais cela a le désagréable effet de dégrader la performance percue de l'application, puisque beaucoup de code à tendance à être executé sur la Thread UI.

HttpWebRequest à la rescousse ?

Vous trouverez que HttpWebRequest est un meilleur choix pour cela. Cette classe utilise le pattern Begin/End et l'exécution d'un AsyncCallback est effectuée dans le contexte du Thread Pool. Cela fait en sorte que le code executé dans ce callback ne va naturellement pas impacter la performance percue de l'application.

En utilisant les Reactive Extensions, cela peut être écrit come ceci :

1
2
3
4
5
6
7
8
9
10
11
var request = WebRequest.Create("http://www.google.com");

var queryBuilder = Observable.FromAsyncPattern(
(h, o) => request.BeginGetResponse(h, o),
ar => request.EndGetResponse(ar));

queryBuilder()
/* Perform the expensive work in the context of the AsyncCall back */
/* from the WebRequest. This will be the ThreadPool. */
.Select(response => DoSomeExpensiveWork(response))
/* Go back to the UI Thread to execute the OnNext method on the subscriber */
.ObserveOnDispatcher()
.Subscribe(result => DisplayResult(result));

De cette manière, votre code s'exécutera pour la plupart hors de la thread UI, la où cela n'impacte pas l'expérience utilisateur.

Pourquoi ce ne serait pas à la rescousse alors ?

En fait, ca le sera toujours (jusqu'a NoDo), mais il y a un détail à noter. Et c'est un détail important, du point de vue performance.

Si l'on considère ce code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public App()
{
/* some application initialization code */
ManualResetEvent ev = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(
d =>
{
var r = WebRequest.Create(http://www.google.com);
r.BeginGetResponse((r2) => { }, null);
ev.Set();
}
);
ev.WaitOne();
}

Ce code effectue la création d'une requête sur le ThreadPool, tout en bloquant la Thread UI dans le fichier App.xaml.cs. Cela fait en sorte que la construction (mais pas l'appel sur le réseau) de la WebRequest est synchrone, et donc que l'application attent que la requête commence avant d'afficher une page à l'utilisateur.

Bien que ce code ne soit pas dans les meilleures pratiques, il y avait un chemin de code dans l'application Flickr 1.3 qui faisait quelque chose de très similaire, mais d'une manière plus complexe. Si vous l'essayez par vous même, vous verrez que l'application s'arrête au démarrage, ce qui signifique que l'évènement n'est jamais levé.

Qu'est-ce qui se passe ?

En creusant un peu, on peut voir qu'une thread du ThreadPool est arrêtée avec la Stack Trace suivante :

mscorlib.dll!System.PInvoke.PAL.Threading_Event_Wait()
mscorlib.dll!System.Threading.EventWaitHandle.WaitOne()
System.Windows.dll!System.Windows.Threading.Dispatcher.FastInvoke(...)
System.Windows.dll!System.Net.Browser.AsyncHelper.BeginOnUI(...)
System.Windows.dll!System.Net.Browser.ClientHttpWebRequest.BeginGetResponse(...)
WindowsPhoneApplication2.dll!WindowsPhoneApplication2.App..ctor.AnonymousMethod__0(...)

La méthode BeginGetResponse est en train d'essayer d'exécuter quelque chose sur la Thread UI. Et dans notre exemple, puisque la Thread UI est bloquée par le ManualResetEvent, l'application est bloquée dans un deadlock entre un lock du dispatcher et notre évènement.

C'est aussi vrai pour la méthode EndGetResponse.

Mais si l'on creuse encore plus loin, on peut voir dans la version de System.Windows.dll dans l'émulateur WP7 (celle du SDK ne contient que des stubs des types publics), que la méthode BeginGetResponse force en fait la totalité de son travail de création de la requête web sur la Thread UI !

C'est particulièrement perturbant. Je me demande encore pourquoi du code qui n'a attrait qu'au réseau a besoin de s'exécuter sur la thread UI.

Quel est l'impact alors ?

L'impact est assez simple : Plus il y a de requête d'initiées, moins l'interface utilisateur sera réactive, à la fois pour le traitement du lancement et de la fin d'une requête web. Chaque appel aux méthodes BeginGetResponse et EndGetResponse s'executent implicitement sur la thread UI.

Dans le cas de l'applications de controle à distance comme la mienne, qui essayent d'avoir le controle à distance de la souris, ces applications sont toutes affectées par la même piètre qualité de réponse de la souris. C'est en partie à cause de la thread UI qui est occupée à traiter les évènements de manipulation, ce qui explique beaucoup les problèmes de performance des requêtes web effecutées en même temps, même avec l'utilisation de HttpWebRequest au lieu de WebClient. Cela explique aussi que tant que l'utilisateur touche l'écran, les requêtes web seront fortement ralenties.

Le problème d'écran noir de l'application Flickr 1.3

Dans l'application Flickr sur laquelle j'ai pu travailler, beaucoup d'utilisateurs rapportaient un problème d'écran noir au démarrage, après que l'application ai bien fonctionné plusieurs jours.

L'application était en fait en train d'essayer de mettre à jour une ressource de manière asynchrone en utilisant HttpWebRequest. A cause d'une race condition entre une ressource de l'application et la Thread UI qui attendait que l'application démarre, le résultat a été un "écran noir" infini qui ne pouvait être résolu qu'en réinstallant l'application.

En passant, à ce point dans l'initialisation de l'application, dans le constructeur de la classe App, l'application n'est pas tuée après 10 secondes si elle n'affiche rien à l'utilsateur. Par contre, si l'application bloque dans le constructeur de la premiere page, l'application est terminée automatiquement par l'OS après environ 10 secondes.

Enfin, à propos de l'utilisation de la Thread UI à l'intérieur de HttpWebRequest, les applications qui utilisent beaucoup le réseau pour récupérer de petites ressources comme des images, cela a un impact négatif sur la performance.

Est-ce que je peut faire quelque chose ?

Lors de l'analyse du code de System.Windows.dll, j'ai pu remarquer que BeginGetResponse vérifie que la thread courante est le dispatcher, et si c'est le cas, l'execution n'est pas poussée sur le dispatcher.

Cela veut dire qu'il est possible de grouper les appels à BeginGetResponse sur la Thread UI, ce qui évitera de changer trop souvent de contexte. Ce n'est pas la panacée, mais c'est le moins que l'on puisse gagner de ce coté.

Et dans les futures version de Windows Phone ?

Du coté des bonnes nouvelles, Scott Gu a annoncé durant le Mix 11 que les évènements de manipulation seront déplacés hors de la Thread UI, faisant en sorte que l'interface utilisateur sera "glissante comme du beurre". Beaucoup d'applications vont bénéficier de ce changement. Du coté du réseau, mis à part l'introduction des sockets, rien n'indique que les webrequests seront sorties du UI thread.

Mais attendons Mango, je suppose que tout ceci va changer pour le meilleur, et nous permettra de développer des applications performantes pour Windows Phone.

[WP7Dev][Reactive] Rendre les Reactive Extensions Plus Stables

This article is available in english.

Lorsque l’on développe des applications .NET, les exceptions non gérées dans des threads ont le désagréable effet de terminer le processus courant.

Dans l’exemple suivant :

1
2
3
4
5
6
7
8
9
10
11
12
    static void Main(string[] args)
{
var t = new Thread(ThreadMethod);
t.Start();

Console.ReadLine();
}

private static void ThreadMethod()
{
Thread.Sleep(1000); throw new Exception();
}

Cette exception très simple va invariablement terminer le processus, et pour empêcher cela, l’exception doit être gérée correctement :

1
2
3
4
5
6
7
8
9
10
11
    private static void ThreadMethod()
{
try
{
Thread.Sleep(1000); throw new Exception();
}
catch (Exception e)
{
// TODO: Log and report the exception
}
}

Cela rend des classes comme System.Threading.Thread, System.Threading.Timer ou System.Threading.ThreadPool très dangereuses à utiliser si l’on veut une application toujours en fonctionnement. Il est alors requis qu’aucune exception ne sorte non gérée d’un des handlers donnés à une de ces classes.

Même si il est possible, en utilisant l’évènement AppDomain.UnhandledException, d’être notifié lorsqu’une exception a été levée et n’a pas été gérée proprement, la plupart du temps cela mène à l’arrêt de l’application.  Ce comportement a été introduit en .NET 2.0, afin d’éviter que les exceptions non gérées soit ignorées silencieusement.

Ceci étant, bien que cela soit un comportement par défaut tout à fait approprié, dans un environnement d’entreprise, je fais en sorte d’imposer des règles d’analyse statique de code (via VS2010 ou NDepend) qui empêchent l’utilisation en direct de ces classes. Cela permet de forcer l’utilisation de classes wrappers qui interceptent les exceptions avec un filtre très large afin de les logger et de les rapporter, mais qui ne termine pas le processus.

Bien entendu, cela dénote la présence d’un véritable bug qui doit être analysé, puisque les exceptions ne doivent pas être gérées aussi tard.

 

Le cas du Reactive Framework

Dans Silverlight pour Windows Phone 7, et dans n’importe quelle application .NET 3.5 or .NET 4.0 qui utilise les Reactive Extensions, il est très simple de changer de threads.

Les opérateurs Réactifs comme Timer, BufferWithTime, ObserveOn ou SubscribeOn permettent l’utilisation de Schedulers spécifiques comme ThreadPool, TaskPool ou NewThread, et si un subscriber ne gère pas l’exception proprement, le processus se terminera aussitôt.

Cet exemple adapté au Reactive Framework termine l’application :

1
2
3
4
5
6
7
8
9
10
11
12
    static void Main(string[] args)
{
Observable.Timer(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10))
.Subscribe(_=> ThreadMethod());

Console.ReadLine();
}

private static void ThreadMethod()
{
throw new Exception();
}

L’opérateur Observable.Timer utilise la classe System.Threading.Timer et cela la rend autant vulnérable aux problèmes de terminaison. Chaque subscriber doit gérer les exceptions lancées dans le delegate OnNext, ou l’application se terminera.

Aussi, ne pensez pas que le delegate OnError passé à la méthode Observable.Subscribe va gérer les exceptions levées durant l’execution du code dans OnNext. OnError va simplement notifier des erreurs levées durant l’execution des operateurs précédents, et non pas le courant.

La Méthode d’extension IScheduler.AsSafe()

Malheureusement, il n’est pour l’instant pas possible de surcharger le scheduler par défaut utilisé en interne par les opérateurs Réactifs. Le seul moyen de gérer toutes les exceptions non gérées est d’utiliser l’opérateur ObserveOn, et d’intercepter les appels à la méthode IScheduler.Schedule. Les appels peuvent alors être décorés avec des gestionnaires d’exceptions qui loggent et raportent l’exception, sans terminer le processus.

Donc, pour généraliser ce comportement de logging et de rapport d’erreur, j’ai donc créé la méthode d’extension AsSafe() que l’on peut placer tout en haut d’une expression Réactive :

1
2
3
    Observable.Timer(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10))
.ObserveOn(Scheduler.ThreadPool.AsSafe())
.Subscribe(_=> ThreadMethod());

Et voici le code de cette très simple extension :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static class SafeSchedulerExtensions
{
public static IScheduler AsSafe(this IScheduler scheduler)
{
return new SafeScheduler(scheduler);
}

private class SafeScheduler : IScheduler
{
private IScheduler _source;

public SafeScheduler(IScheduler scheduler) {
this._source = scheduler;
}

public DateTimeOffset Now { get { return _source.Now; } }

public IDisposable Schedule(Action action, TimeSpan dueTime)
{
return _source.Schedule(Wrap(action), dueTime);
}

public IDisposable Schedule(Action action)
{
return _source.Schedule(Wrap(action));
}

private Action Wrap(Action action)
{
return () => {
try {
action();
}
catch (Exception e) {
// Log and report the exception.
}
};

}
}
}

[WP7] Utiliser un compte Exchange avec un certificat personalisé

Posté le dimanche 1 août 2010 23:13 par jay :: 0 commentaire(s)
Classé sous

Selon la compagnie pour laquelle vous travaillez, vous avez peut-être à vous connecter à votre serveur Exchange en utilisant un certificat serveur auto-signé, pour être utilisé avec le protocole HTTPS (soit avec TLS ou SSL).

Si vous êtes suffisamment malchanceux pour être dans cette situation, si vous utilisez un navigateur internet récent,vous pouvez installer ce certificat soit directement dans le store de Windows, soit dans le browser. Il est possible de faire cela dans IE8 en utilisant cette très longue méthode.

Mais si vous êtes sur un Windows Phone 7 (ou un Windows Mobile 6.x d'ailleurs), et bien lorsque vous essayerez de connecter votre téléphone à Exchange, vous aurez un gentil message d'erreur expliquant qu'il y a un problème de certificat avec le serveur. Le problème est ni Internet Explorer ni les outils d'Exchange ne fournissent la possibilité d'installer ce certificat personnalisé. Et il n'y a pas non plus la possibilité d'accéder au système de fichiers.

Heureusement, il est possible de s'envoyer à soi-même ce certificat par email, et le client mail de WP7 à la capacité d'installer ces certificats !

Donc, en utilisant toujours cette très longue méthode pour exporter le certificat dans le format ".cer" (depuis un PC en se connectant au serveur Exchange via son adresse HTTPS depuis Internet Explorer), envoyez le vous à vous-même, puis installez le avec un "tap" sur le fichier attaché.

Vous pouvez maintenant apprécier la lecteur de vos emails et calendriers du travail durant les weekends, au cas ou vous n'avez rien de mieux à faire !

Revisité avec les Reactive Extensions: DataBinding et Mise à Jour depuis Plusieurs Threads

This article is available in english.

Récemment, j'ai écrit un article à propos des WinForms, DataBinding et Mises à Jour depuis plusieurs Threads, où j'expliquais comment externaliser l'exécution d'une méthode accrochée à un événement sur la thread de l'interface utilisateur.

J'ai utilisé alors une technique basée sur un Action<Action> qui prend avantage des "Closures", et le fait que l'action va transporter son contexte jusqu'à l'endroit où il doit être exécuté.

Ce concept d'externalisation peut être revisité avec les Reactive Extensions, et l'interface IScheduler.

L'exemple original

Voyons l'exemple de code original :

1
2
3
4
5
    public MyController(Action<Action> synchronousInvoker)
{
_synchronousInvoker = synchronousInvoker;
...
}

Ce code est le constructeur du contrôleur du formulaire, et l'action "synchronousInvoker" va être définie comme ceci :

1
    _controller = new MyController(a => Invoke(a));

Et utilisée comme ceci :

1
2
3
    _synchronousInvoker(
() => PropertyChanged(this, new PropertyChangedEventArgs("Status"))
);

Ou "Invoke" est en fait Control.Invoke(), utilisé pour exécuter du code sur la Thread de l'interface graphique, la où les mise à jour de contrôles graphiques peuvent être effectuées sans problèmes.

Bien que la technique Action<Action> fonctionne très bien pour parvenir à isoler les rôles, il n'est pas très évident, juste en regardant le constructeur, de savoir ce que l'on doit donner à ce fameux paramètre.

Utiliser l'interface IScheduler

Pour parvenir à isoler l'exécution du contenu des opérateurs du Reactive Framework, l'équipe du Rx a introduit le concept de Scheduler, avec un tas d'implémentations de schedulers communs.

Cela permet d'exposer simplement un moyen de planifier l'exécution d'une méthode dans le contexte qui est n'est pas à la charge de l'utilisateur. Bien souvent, le code qui utilise le scheduler ne veut pas s'occuper du contexte dans lequel est exécute le code. Dans notre cas, on veut exécuter notre code sur la pompe à message des WinForms, et ça tombe bien, "il y a un Scheduler pour ca".

L'exemple précédent peut être facilement mis à jour en utilisant IScheduler à la place de Action<Action>, et d'utiliser la méthode IScheduler.Schedule().

1
2
3
4
5
    public MyController(ISheduler scheduler)
{
_scheduler = scheduler;
...
}

Et de remplacer l'appel par :

1
2
3
    _scheduler.Schedule(
() => PropertyChanged(this, new PropertyChangedEventArgs("Status"))
);

Ce n'est pas une modification très complexe, mais cela le code bien plus compréhensible.

On peut utiliser le Scheduler fourni pour les WinForms, le non-documenté System.Concurrency.ControlScheduler (qui n'est pas présent dans la classe Scheduler parce qu'il ne peut pas être créé statiquement car il requiert une instance de Control) :

1
    _controller = new MyController(new ControlScheduler(this));

Avec "this" qui est une instance de Control.

Au final, le code est plus lisible, et pour ce qui est de faire des tests unitaires sur le Contrôleur, c'est tout à fait faisable avec le System.Concurrency.CurrentThreadScheduler, puisqu'il n'y a pas besoin de changer de threads dans les tests.


Que se passe-t-il avec les Reactive Extensions et Silverlight pour Windows Phone 7 ?

Dans un refactoring très étrange, l'équipe du WP7 a déplacé le IScheduler depuis System.Concurrency vers le très spécifique namespace Microsoft.Phone.Reactive.

Je ne comprend pas vraiment la raison d'un tel changement, et cela a le désagréable effet de rendre incompatible du code qui compilait facilement sur plusieurs plateformes.

Ils ont peut-être considéré que l'implémentation des Reactive Extensions pour Windows Phone était trop différente de la version Desktop... Mais le Compact Framework a été construit sur cette assomption, et la plupart du code est resté dans les même namespaces.

Si quelqu'un a une explication pour ce refactoring étrange, je suis tout ouïe :)

Utiliser le Remote Debugger de Visual Studio

Posté le mardi 20 juillet 2010 22:55 par jay :: 0 commentaire(s)
Classé sous , ,
Pour continuer dans la veine des fonctionnalités de Visual Studio qui existent depuis un bon bout de temps, mais qui sont communément sous utilisées, je vais parler dans ce post du Remote Debugger.

Débugger en Local

Visual Studio comporte un débogueur qui permet de débugger un programme que l'on lance avec F5, ou "Debug / Start Debugging". Visual Studio va alors se placer dans un mode spécial qui permet d'effectuer du pas à pas dans le programme, utiliser des fonctionnalités telles que les BreakPoints, TracePoints, Watch, Intellitrace, faire des minidumps, et bien d'autres fonctionnalités.

Le Debugger lance donc le programme sur la machine locale, en utilisant les permissions de l'utilisateur loggé qui a lancé Visual Studio.

Rien de bien extraordinaire. Quoique que le Reverse Debugging avec IntelliTrace dans VS2010, ca vaut quand même bien son pesant d'or.

Spécifités Matérielles et CrapWare

Je ne sais pour vous, mais je tiens mon PC de développement aussi stable que possible. Je n'installe que très rarement de nouveaux logiciels afin de garder des performances relativement stable au cours du temps. Je vais d'ailleurs généralement installer de nouvelles version de logiciels uniquement après les avoir testées sur un autre PC pour déterminer leur comportement.

Traitez moi de maniaque autant que vous voulez, c'est assumé :)

Mais que faire lorsque l'on a besoin de tester un programme d'installation par exemple ? Ou bien que faire lorsque l'on a besoin de debugger des extensions de logiciels comme NI TestStand ou LabView ? Ou bien encore que faire lorsque le programme a besoin d'un matériel très spécifique qui ne peut pas être installé sur la machine de développement ? (Rainbow Keys, anyone ?)

Réponse : Le Remote Debugger ! Lorsque cela est possible, je vais tester et debugger mon logiciel dans une machine virtuelle, ou bien sur une machine physique qui comporte l'environnement nécessaire à l'exécution du programme.

De cette manière, mon environnement de développement reste stable, et je n'ai pas d'installations à faire qui pourraient venir m'installer du crapware et me manger le peu de mémoire vive qui me reste :)

Le Remote Debugger ?

L'idée est de continuer à utiliser sa machine de développement, la où les sources sont placées, et de se connecter via le réseau à la machine qui doit exécuter le programme. Ensuite, la session de debug distante est très similaire à une session locale, à l'exception que le "Edit and Continue" n'est pas supporté. Mais on peut généralement généralement s'en passer.

Lancer le debugging depuis Visual Studio

Il est possible de lancer directement l'exécutable sur la machine distante en utilisation l'option "Use Remote Machine" dans l'onglet "Debug" d'un projet C#. Il faut faire attention cependant que cocher cette case implique que les chemins précisés dans "Working Directory" deviennent ceux de la machine distante.

Il faut aussi prendre en compte que Visual Studio ne va pas copier les binaires et fichiers PDB sur la machine distante. C'est donc à vous de faire la copie des fichiers au bon endroit, en utilisant une "Post Build Action" un chemin UNC comme ceci "\\mymachine\c$\temp".

S'attacher à un processus en cours d'exécution

Il est aussi possible de s'attacher à un processus en cours d'exécution, en utilisant l'option "Debug / Attach To Process". Il suffit alors de changer le "Qualifier" et de mettre le nom du remote debugger distant, et de choisir le process à debugger.

Petite astuce ici : La case "Show processes from all users" n'est pas cochée par défaut. Cela veut dire que si vous tentez de débugger un service Windows, vous ne verrez pas le process tant que cette case ne sera pas cochée.

Notez aussi que cette fenêtre, "Attach To Process", fonctionne très bien pour les process locaux. C'est une fonctionnalité très pratique pour aller faire un dump mémoire d'un process qui prend trop de mémoire par exemple, à défaut d'utiliser windbg.

Installer le Remote Debugger

Le Remote Debugger est un composant additionnel de Visual Studio situé sur le média d'installation dans le répertoire "Remote Debugger". Il existe en trois version : x86, x64 et ia64. Si vous êtes amenés a débugger des programmes 32 bits sur une machine 64 bits, je vous conseille d'installer les deux versions x86 et x64. Vous aurez à choisir quel remote debugger lancer selon le type de Runtime .NET utilisé (32 bits ou 64 bits). Vous pourrez voir quel version utiliser avec la colonne "Type" dans la fenêtre "Attach To Process".

Enfin, voici les étapes à suivre :
  • Si vous utilisez VS2008 SP1, vous pouvez le télécharger ici, pour VS2010 utilisez l'installeur situé sur le DVD.
  • Une fois installé sur la machine distante, installez le service RDBG en utilisant le compte LocalSystem avec l'assistant.
  • Vous aurez peut-être un message à propos de la sécurité. Si vous l'avez, suivez ces étapes :
    • Ouvrez le panneau "Local Security Policy" dans le panneau de configuration "Administrative Tools"
    • Allez dans "Local Policies" / "Security Options"
    • Double clickez sur "Network access: Sharing and security model for local accounts" et mettez la valeur "Classic : Local users authenticate as themselves"
    • Fermez la fenêtre
  • Si votre machine distante n'est pas sur le même domaine que votre machine de développement, ou bien encore ne fait pas partie d'un domaine, ajoutez sur la machine distante un compte utilisateur local dont le nom est le même que votre nom d'utilisateur qui est loggé sur votre machine de développement, et placez le dans le groupe Administrateurs. Les mots de passe doivent également être les mêmes.
  • Démarrez le remote debugger sur la machine distante. Notez que si vous voulez débugger une application 32 bits sur la machine distante, vous aurez à lancer la version 32 Bits du remote debugger (msvsmon.exe)
  • Sur la machine de développement, ouvrez la fenêtre "Attach to process" et tapez le nom l'identifiant du remote debugger (affiché dans la fenêtre du remote debugger). Cela devrait ressembler à quelque chose comme administrator@my-machine.
Notez que le Firewall de la machine de développement comme celui de la machine distante peut-être un frein au bon fonctionnement du Remote Debugger. Vous pouvez le désactiver temporairement pour faciliter le débugger, mais pensez à le réactiver après. Si vous ne voulez activer que les ports nécessaire, le port 135/TCP est utilisé, puisque le Remote Debugger utiliser DCOM comme protocole de communication.

Et si mes BreakPoints reste des ronds rouge vides ?

C'est une situation assez commune, qui signifique que les fichiers pdb ne correspondent pas aux binaires chargés en mémoire. Vérifiez que vous copiez bien les pdb en meme temps que les dll.

La fenetre "Debug / Windows / Modules" permet de savoir si les symboles de debug ont bien été chargés, et si ce n'est pas le cas, la fenêtre "View / Output / Debug" va généralement donner la raison du problème de chargement.


Happy debugging !

Versionner Efficacement avec les Attributs AssemblyVersion et AssemblyFileVersion

Posté le samedi 10 juillet 2010 17:22 par jay :: 3 commentaire(s)
Classé sous ,

This article is available in english.

On parle assez facilement des dernières technologies et des choses que l'on vient d'apprendre parce que c'est comme ca que les geeks fonctionnent, mais pour les nouveaux entrants, ce n'est pas toujours simple. C'est un débat assez large et récurrent, et pour ma part j'estime qu'il est bon de revenir de temps à autres sur les bonne pratiques pour ces nouveaux entrants.

Les attributs AssemblyVersion et AssemblyFileVersion

Lorsque l’on veut donner une version à une assembly .NET, deux moyens existent :

D’une manière générale, et par défaut dans les templates de Visual Studio 2008, on trouve le fichier AssemblyInfo.cs dans la section Properties d'un projet. Ce fichier ne va généralement contenir que l'attribut AssemblyVersion, ce qui va faire en sorte que la valeur par défaut de AssemblyFileVersion va être celle de AssemblyVersion. L'attribut AssemblyFileVersion est maintenant ajouté dans les templates de projets C# dans Visual Studio 2010, ce qui est une bonne chose.

Il est possible de voir la valeur de AssemblyFileVersion dans les propriétés de fichiers dans l'explorateur de fichiers de Windows, ou en ajoutant la colonne "File Version", toujours dans l'explorateur.

On va aussi trouver une numérotation automatique fournie par le compilateur, sous la forme de :

[assemblyAssemblyVersion("1.0.0.*")]

Chaque nouvelle compilation va donner une nouvelle version.

Cette notation peut convenir dans un premier temps, mais dès que l'on commence à avoir des projets un peu complexes, on va très rapidement introduire de l'intégration continue qui fournit des nightly builds. On va généralement vouloir donner une version à ses assemblies de manière à ce qu'il soit simple de retrouver la révision utilisée dans le système de sources pour compiler les assemblies.

On modifie alors les scripts de build dans Team Build de façon à utiliser des taches comme AssemblyInfo des MSBuild Tasks, et générer ainsi un nouveau fichier AssemblyInfo.cs qui contiendra la bonne version.


Publier un nouvelle version d'une assembly

Pour revenir sur l'utilité de versionner proprement une assembly, on veut généralement savoir assez rapidement, lorsque l'on a publié une build d'un projet, quelle est la version installée chez un client. Très souvent, on veut savoir la version publiée chez un client parce qu'il y a un problème, et qu'il faudra probablement fournir un correctif sous la forme d'une nouvelle assembly. Surtout si l'on ne peut pas réinstaller entièrement le logiciel chez le client.

Un Exemple plus ou moins réel

Prenons pour exemple que l'on dispose d'une solution avec deux assemblies signées avec un strong name, Assembly1 et Assembly2, avec Assembly1 qui utilise des types présents dans Assembly2, et qui enfin sont chacune versionnées avec AssemblyVersion avec la version 1.0.0.458. Ces assemblies font parties d'une build officielle publiée chez un client.

Si l'on veut fournir un correctif dans Assembly2, on va généralement créer une branche dans le système de gestion de sources pour la révision 458 soit 1.0.0.458, puis faire le correctif dans cette branche ce qui donnera la révision 460, soit 1.0.0.460.

Si l'on laisse le Système de build compiler cette nouvelle révision, on va avoir des assemblies qui comporteront la version 1.0.0.460. Si l'on prend juste assembly2 et que l'on place cela dans l'installation du client, le CLR ne chargera pas cette nouvelle version de l'assembly, car Assembly1 requiert d'avoir Assembly2 avec la version 1.0.0.458. On peut utiliser le paramètre bindingRedirect dans le fichier de configuration de l'application, mais cela n'est pas toujours pratique et si l'on modifie beaucoup d'assemblies, cela devient lourd à gérer.

On peut aussi compiler cette nouvelle version en changeant l'AssemblyVersion de 1.0.0.460 à 1.0.0.458 pour Assembly2, mais cela a le désavantage de faire en sorte que la version de cette assembly n'est plus vraiment la bonne, et cela complique énormément le diagnostic lors d'un éventuel problème suivant, pour savoir quelle version est effectivement installée.


Utiliser AssemblyFileVersion en plus

Pour éviter d'avoir ces problèmes de versions lors de la résolution des dépendances, il est possible de garder de garder constant le AssemblyVersion, et d'utiliser le AssemblyFileVersion pour donner la version effective de l'assembly.

La version présente dans le AssemblyFileVersion n'est pas utilisée par le Runtime .NET, mais est affichée dans les propriétés du fichier dans l'explorateur de Windows.

On va donc avoir dans le fichier AssemblyVersion la version originale de l'application, et placer la même version dans AssemblyFileVersion, puis on va changer le AssemblyFileVersion au fur et à mesure des différents correctifs publiés pour ces assemblies.

Microsoft utilise cette technique pour versionner les assemblies de .NET, puisque si l'on prend l'assembly System.dll pour .NET 2.0, on peut constater que le AssemblyVersion est 2.0.0.0, et que le AssemblyFileVersion est par exemple 2.0.50727.4927.


D'autres exemples de problèmes versions (Edit du 2010/07/11)

On peut trouver aussi certains autres cas de problèmes de chargement liés à la différence de version d'une assembly chargée par rapport à celle utilisée à la compilation.

Les Custom Behaviors de WCF

WCF donne la possibilité de fournir des custom behaviors pour altérer le comportement par défaut des bindings, et il est nécessaire de fournir le nom qualifié au complet et sans erreurs. C'est un bug assez ennuyeux dans WCF 3.x car il est assez complexe à débugger, et c'est un très bon exemple d'utilisation de nécessité de désactiver le "Just My Code" pour savoir pourquoi notre assembly ne se charge pas correctement.

Une bonne nouvelle cependant, ce bug très ancien a été corrigé dans WCF 4.0 !

Les Générateurs de Proxy Dynamiques

Certains générateurs de proxys dynamiques tels que Castle Dynamic Proxy 2 ou Spring.NET utilisent les types qualifiés à la source pour générer les proxy, et des problèmes de chargement peuvent se produire si l'assembly référencée par le proxy n'est pas exactement celle qui est chargée, avec ou sans Strong Name. Ces frameworks sont très utilisés lorsque l'on veut utiliser de l'AOP, ou bien dans des outils comme nHibernate, ActiveRecords ou iBatis.

Plus précisement, l'utilisation de la méthode ProxyGenerator.CreateInterfaceProxyWithTarget génère un proxy qui cible l'assembly contenant type référencé lors de la génération de l'interface proxisée.

Pour donner un exemple, prenons l'exemple d'une interface I1 dans une assembly A1(1.0.0.0), qui a une méthode qui utilise un type T1 dans une assembly A2(1.0.0.0). Si l'on change l'assembly A2 et que sa version devient A2(2.0.0.0), le proxy ne sera pas généré proprement puisque la référence T1/A2(1.0.0.0) sera utilisée puisque compilée dans A1(1.0.0.0), alors que l'on vient de charger la nouvelle version A2(2.0.0.0).

La bonne pratique de ne pas changer le AssemblyVersion permet de s'éviter des problèmes de ce genre. Ce n'est pas incontournable, mais c'est du travail en plus.

Et vous ?

Il ne s'agit bien sur que d'un exemple de "bonne pratique", qui fait semble avoir fait ses preuves.

Et vous, que faites-vous ? Quelles pratiques adoptez vous pour versionner vos assemblies ?


[VS2010] A propos de "Just My Code" et de son Influence sur le Debugger

Posté le lundi 5 juillet 2010 22:24 par jay :: 0 commentaire(s)
Classé sous , ,

This article is available in english.

La fonctionnalité “Just My Code” est présente depuis un bon moment dans Visual Studio. Depuis Visual Studio 2005, en fait. Et il est facile d'en manquer quelques subtilités.

Grossièrement, cette fonctionnalité permet de ne montrer que votre code, et principalement les assemblies qui sont en mode Debug (pas optimisées) et qui ont des symboles de Debug (fichiers pdb). La plupart du temps c'est intéressant lorsque l'on Debug du code relativement simple.

Mais lorsque l'on a à traiter des problèmes plus complexes où l'on veut intercepter les exceptions qui peuvent être relancées depuis des parties du code qui ne sont pas "Just My Code", alors il devient nécessaire de la désactiver.

Si vous êtes un développeur .NET expérimenté, il y a de grandes chances que vous l'ayez désactivée parce que cela vous a ennuyé à un moment. Je l'avais fait aussi, jusqu'à récemment.


Gestion des Exceptions par le Debugger

La fonctionnalité “Just my Code” (que j'appellerais JMC pour le reste de l'article) change quelques comportements dans la manière dont le debugger gère les exceptions.

Si elle est activée, on peut alors voir deux colonnes dans le menu “Debug / Exceptions” :

  • Thrown, qui veut dire que le debugger va s'arrêter au moins profond rethrow dans la stack qui a généré l'exception
  • User-unhandled, qui veut dire que le debugger va s'arrêter si l'exception n'a pas été gérer par un gestionnaire d'exception dans du code utilisateur de la stack courante.

Si elle est activée, alors la même boite de dialogue n'affiche qu'une seule colonne :

  • Thrown, qui fait en sorte que le debugger s'arrête dès que l'exception est lancée


Vous aurez probablement remarqué une différence de taille dans la manière dont le debugger interprète l'option "Thrown".  Pour être un peu plus clair à propos de cette différence, regardons cet exemple de code :

1
2
3
4
5
6
7
8
9
10
11
12
13
    static void Main(string[] args) 
{
try
{
var t = new Class1();
t.Throw();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Exécutable Principal, en configuration Debug avec les symboles de debug activés

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public class Class1 
{
public void Throw()
{
try
{
Throw2();
}
catch (Exception e)
{
throw;
}
}
private void Throw2()
{
throw new InvalidOperationException("Test");
}
}
Assembly différente, en configuration Debug, mais sans Symboles de Debug.

Si l'on exécute ce code avec le debugger, l'option JMC activée et la colonne "Thrown" cochée pour l'exception “InvalidOperationException”, voici la stack trace affichée pour l'exception :

    NotMyCode.dll!NotMyCode.Class1.Throw() + 0x51 bytes
> MyCode.exe!MyCode.Program.Main(string[] args = {string[0]}) Line 15 + 0xb bytes


Et voici la même chose avec la fonctionnalité JMC désactivée :

    NotMyCode.dll!NotMyCode.Class1.Throw2() + 0x46 bytes
NotMyCode.dll!NotMyCode.Class1.Throw() + 0x3d bytes
> MyCode.exe!MyCode.Program.Main(string[] args = {string[0]}) Line 15 + 0xb bytes

Vous aurez remarqué l'impact de "moins profond rethrow dans la stack", et cela veut dire que si vous activez le JMC, vous n'aurez pas l'emplacement original de l'exception.

Vous vous demandez peut-être pourquoi il est intéressant d'avoir l'emplacement original de l'exception. C'est une technique de debuggage communément utilisée pour trouver des problèmes pointus, liés à des exceptions levées très loin dans du code qui n'est pas à nous, et on peut trouver une exception comme TypeInitializerException parmi celles ci. Il peut être très utile de s'arrêter à l'endroit de l'exception originale pour avoir le bon contexte, ou la stack qui a mené à cette exception.

Récemment, j'ai utilisé cette technique de "Break on all exceptions" sans JMC pour corriger un problème de chargement d'assemblies 32 Bits dans un CLR 64 Bits. On ne sait pas exactement quelle exception on recherche, et avoir le JMC qui cache des exceptions n'est pas de la plus grande aide.

Aussi, pour être complet, un debugging plus intense se fait avec WinDBG et l'extension SOS (et un bon aide mémoire sur SOS). Mais c'est un autre sujet.


L'expérience de Debugging de Step Into avec JMC

Si vous avez lu jusque là, vous vous demandez peut-être l'intérêt d'activer JMC. Après tout, vous pouvez gérer vous même votre code, et avec un peu d'expérience il est facile d'ignorer mentalement les parties de la stack qui ne sont pas les vôtres. En fait, la couleur grise des parties de code qui n'ont pas de symboles de debugging aident beaucoup.

Et bien, voici un bon exemple de l'utilisation de JMC : La fonctionnalité "Step Into" du debugger. Une fonctionnalité très simple qui permet de faire du pas à pas durant le debuggage du code.

Si vous être en cours de debugging, vous pourrez entrer dans le code qui est appelé à la lige suivante et si cela est possible, voir ce qui est dedans.

Pour expliquer cela, prenons cet exemple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    static void Main(string[] args) 
{
var myObject = new MyObject();

Console.WriteLine(myObject);
}

class MyObject
{
public override string ToString()
{
return "My object";
}
}

Ce programme très simple va utiliser le fait que Console.WriteLine va appeler la méthode virtuelle ToString sur l'objet qui lui est passé en paramètre.

Le but de cet exemple est de faire que "My Code" (Main) appelle un morceau de "Not My Code" (Console.WriteLine) qui lui même appelle "My Code" (MyObject.ToString). Facile.

Si l'on exécute ce code avec le JMC désactivé, et si l'on essaye de faire "Step Into" dans Console.WriteLine, on va en fait sauter l'appel. Ce n'est pas très utile si l'on veut debugger notre code, celui qui est dans le MyObject.ToString.

Un exemple très concret de manque de "Step Into" peut être trouvé lors de l'utilisation de proxies comme ceux trouvés dans Spring.NET ou Castle's DynamicProxy, puisqu'ils interceptent les appels avec du code sans symboles de Debug. Il n'est alors pas possible de faire du "Step Into" dans les méthodes des objets qui ont été "proxisés" pour faire de l'AOP, par exemple.

En revanche, si l'on active JMC, on peut faire un "Step Into" dans notre propre code, même si la vraie méthode suivante n'est pas une des nôtres et qu'elle est ignorée.

Mots de la fin

Utiliser JMC dans ce contexte est particulièrement utile et naturel, ce qui fait du sens de l'avoir activée par défaut dans VS20xx. C'est une fonctionnalité qui a été présente depuis tellement longtemps que j'en avait manqué son utilité originale. Cette option m'avait très certainement empêché de faire du debugging efficacement et je l'avais cataloguée comme fonctionnalité "Junior". J'avais eu tort...

Enfin, dans Visual Studio 2010, le JMC a été quelque peu amélioré, puisqu'il est maintenant possible d'y accéder très facilement par le panneau "Show Calls View" d'IntelliTrace.

Il est temps de passer à Visual Studio 2010, les gens ! :)

[WP7Dev] Utiliser le WebClient avec les Reactive Extensions pour Télécharger en Asynchrone

Posté le jeudi 24 juin 2010 10:32 par jay :: 0 commentaire(s)
Classé sous , , , ,

This article is available in english.

Il y a un framework très intéressant qui s'est glissé dans le SDK pour Windows Phone 7 : Les Reactive Extensions.

C'est en fait un framework assez mal compris, principalement parce qu'il n'est pas simple à maitriser, mais que lorsque vous avez pris la main dessus, il est très très pratique ! J'apprécie particulièrement l'extension MemoizeAll, qui est très utile.

Mais je m'égare.


Un Téléchargement de Chaine de caractère Non-Réactif

Sur le Windows Phone 7, la class WebClient n'a qu'une méthode DownloadStringAsync et a l'évènement DownloadStringCompleted correspondant. Cela veut dire que l'on est forcé d'être asynchrone, pour être cordial avec l'interface graphique et ne pas geler l'application pour l'utilisateur, à cause d'une mauvaise pratique de programmation en étant synchrone sur les appels distants.

Dans un monde sans Reactive Extensions, on aurait quelque chose comme ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void StartDownload()
{
var wc = new WebClient();
wc.DownloadStringCompleted +=
(e, args) => DownloadCompleted(args.Result);

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
myLabel.Text = value;
}

Très simple. Mais rapidement vous allez vous rendre compte que l'exécution de l'évènement DownloadStringCompleted est effectué sur la Thread de l'interface graphique. Cela veut dire que, si pour quelque raison vous avez besoin de faire un long calcul suite à la réception de la chaine, vous allez geler l'interface utilisateur pour la durée du calcul. Et comme Windows Phone 7 est fait pour la fluidité et que vous ne voulez pas être le mauvais élève, vous allez vouloir mettre le calcul dans la queue du ThreadPool.

Mais vous allez aussi avoir à mettre à jour l'interface graphique dans le Dispatcher, donc vous avez besoin de revenir du ThreadPool.

On aura alors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public void StartDownload()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted +=
(e, args) => ThreadPool.QueueUserWorkItem(d => DownloadCompleted(args.Result));

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
// Quelques calculs très longs
Thread.Sleep(1000);

Dispatcher.BeginInvoke(() => myLabel.Text = value);
}

C'est un peu plus complexe. Et on remarquera ensuite qu'il faut gérer les exceptions parceque, en fait, c'est le Web. C'est finalement peu fiable.

Donc, ajoutons la gestion des exceptions :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void StartDownload()
{
WebClient wc = new WebClient();

wc.DownloadStringCompleted += (e, args) => {
try {
ThreadPool.QueueUserWorkItem(d => DownloadCompleted(args.Result));
}
catch (WebException e) {
myLabel.Text = "Error !";
}
};

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
// Quelques calculs très longs...
Thread.Sleep(1000);
Dispatcher.BeginInvoke(() => myLabel.Text = value);
}

Cela commence à devenir relativement complexe. Mais maintenant, vous devez attendre le résultat d'un autre appel de WebClient et afficher les deux résultats.

Aïe. Ok, je vais vous épargner celui la.


Le Même Exemple avec les Reactive Extensions

Les Reactive Extensions traitent les évènements asynchrones comme un flux d'évènements. On souscrit à ce flux et on s'en va, et on laisse le Reactive Framework faire la besogne.

Je vais vous épargner les explications sur la dualité entre IObservable et IEnumerable, parce que Erik Meijer l'explique particulierement bien.

Donc, je recommence avec l'exemple simple, et après avoir ajouté les références vers System.Observable et System.Reactive, on peut télécharger une chaîne de caractères :

1
2
3
4
5
6
7
8
9
10
11
12
public void StartDownload()
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// Quand l'évènement est levé, on sélectionne la chaine et
// on en fait un IObservable<string> à la place
.Select(newString => newString.EventArgs.Result);

// Souscription à l'observable, et on assigne le texte du label
o.Subscribe(s => myLabel.Text = s);


// Démarrage du téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

Cet exemple fait la même chose que le premier exemple. Vous remarquerez l'utilisation de Observable.FromEvent qui permet de transformer un évènement en observable d'évènements. Dans cet exemple, le flux d'évènements ne va en contenir en fait qu'un seul, puisque la fin du téléchargement ne s'effectue qu'une seule fois. Chacune de ces occurrences de l'évènement est alors projetée en utilisant Select, vers une chaîne de caractères qui représente le résultat de la requête web.

Cet exemple simple est un peu plus complexe à cause de la plomberie.

Mais maintenant, on veut supporter les changement de contexte de Threads. Les Reactive Extensions supportent le concept de Scheduler, pour observer un IObserable dans un contexte spécifique.

Don, on peut utiliser un Scheduler comme ceci.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public void StartDownload()
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// On s'assure que l'on est sur le ThreadPool
.ObserveOn(Scheduler.ThreadPool)

// Lorsque l'evenement est levé, on selectionne la chaîne
.Select(newString => ProcessString(newString.EventArgs.Result))

// Maintenant on retourne sur la Thread graphique
.ObserveOn(Scheduler.Dispatcher)

// On souscrit à l'observable et on assigne le text au label
.Subscribe(s => myLabel.Text = s);

wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public string ProcessString(string s)
{
// Un très très long calcul...
return s + "1";
}

Dans cet exemple, on a changé de contexte deux fois pour nos besoins, et maintenant, le code est moins complexe que l'exemple original/

Et maintenant, si l'on veut ajouter la gestion des exceptions :

1
    .Subscribe(s => myLabel.Text = s, e => myLabel.Text = "Erreur ! " + e.Message);

Et vous l'avez :)

Combiner le Résultat de deux Téléchargements

Combiner le résultat de deux opérations asynchrones peut être assez complexe, et vous avez à gérer les exceptions, les rendez-vous et des états complexes. Je n'écrirais pas d'exemple ici, promis, mais je vais vous donner un exemple en utilisant les Reactive Extensions :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

public IObservable<string> StartDownload(string uri)
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// On s'assure qu'on l'on est pas sur la Thread graphique
.ObserveOn(Scheduler.ThreadPool)

// On transforme l'évènement en chaine de caractères
.Select(newString => ProcessString(newString.EventArgs.Result));

wc.DownloadStringAsync(new Uri(uri));

return o;
}

public string ProcessString(string s)
{
// Un calcul très très long !
return s + "<!-- Processing End -->";
}

public void DisplayMyString()
{
var asyncDownload = StartDownload("http://bing.com");
var asyncDownload2 = StartDownload("http://google.com");

// On prend les deux résultats et on les combine lorsqu'ils sont disponibles
var zipped = asyncDownload.Zip(asyncDownload2, (left, right) => left + " - " + right);

// On revient sur la thread principale
zipped.ObserveOn(Scheduler.Dispatcher)

// On souscrit à la chaine et on l'affiche
.Subscribe(s => myLabel.Text = s);
}

Et vous obtiendrez une intéressante combinaison de Google et Bing :)

[WP7Dev] Attention à l’attribut [ThreadStatic] dans Silverlight pour Windows Phone 7

This post is available in english.

En d’autres mots, il n’est pas supporté !

Et le pire est que l’on est même pas averti que ce n’est pas supporté... Le code compile, mais l'attribut n'a aucun effet ! On peut bien entendu lire l’article “the differences between silverlight on Windows and Windows Phone”, mais bon, il est facile de l’oublier. Peut-être qu’une règle d’analyse statique de code pourrait empêcher cela.

Mais enfin, vous voudrez probablement utiliser ThreadStatic parce que vous en avez besoin. Mais comme ce n’est pas supporté, vous pourriez aller vers Thread.GetNamedDataSlot, vous me direz.

Pas de chance. Ce n’est pas supporté non plus.

Cela nous laisse à l’implémentation de notre propre TLS, à la main.

 

Mise à jour de Umbrella pour Silverlight sur Windows Phone

Je suis un grand fan d’Umbrella, et la première fois que j’ai eu à utiliser Dictionary<>.TryGetValue et son magique paramètre “out”, dans l'essai de portage de mon application de Controle à Distance, j’ai décidé de porter Umbrella vers Windows Phone 7. Pour enfin utiliser GetValueOrDefault sans le réécrire, encore.

J’ai réussi à faire passer la majorité des tests unitaires, excepté ceux qui émettent du code, utilisent des fonctionnalités du web, utilisent les sérialiseurs xml et binaires, appellent des méthodes privées via la réflection, et ainsi de suite.

Il y a quelques autres parties qui nécessitaient d’être mise à jour, parceque la classe TypeDescriptor n’est pas disponible en WP7, et il faut passer par un try/catch pour vérifier qu’une valeur est convertible d’un type vers un autre. Mais ce n’est pas vraiment gênant, et cela fonctionne comme attendu.

 

ThreadLocalSource dans Umbrella

Umbrella contient une classe nommée ThreadLocalSource qui encapsule le comportement du TLS, et il est très facile de créer une variable statique de ce type, plutôt que d’utiliser une variable statique ThreadStatic.

Les exemples Quick Start en font ce genre d’utilisation :

1
2
3
4
5
6
7
8
9
10
    ISource<int> threadLocal = new ThreadLocalSource<int>(1);

int valueOnOtherThread = 0;

Thread thread = new Thread(() => valueOnOtherThread = threadLocal.Value);
thread.Start();
thread.Join();

Assert.Equal(1, threadLocal.Value);
Assert.Equal(0, valueOnOtherThread);

La Thread principale place la valeur à 1, et l’autre thread essaye de lire la même variable et elle doit être différente. (La valeur par défaut d’un int, qui est 0).

 

Mise à jour de ThreadLocalSource pour éviter l’utilisation de ThreadStatic

L’implémentation du TLS dans .NET est principalement un dictionnaire de paires de string/object qui est attaché à chaque thread active. Donc pour mimiquer cela, on a simplement besoin de créer une liste de toutes les threads qui ont besoin de stocker quelque chose pour elles-mêmes, et l’encapsuler proprement.

On peut créer une variable comme celle-ci :

1
    private static Tuple<WeakReference, IDictionary<string, T>>[] _tls;

Cette variable est intentionnellement du type d’un tableau pour tenter d’utiliser la localité spatiale en mémoire. Puisque sur cette plateforme il ne devrait pas y avoir beaucoup de threads, il est tout à fait acceptable de parcourir le tableau pour en trouver une. Cette approche tente d’être sans locks, en utilisant un mécanisme de “retry” pour mettre à jour le tableau. Le type WeakReference est utilisé pour éviter de garder une référence vers les threads une fois qu’elles sont terminées.

Donc, pour faire la mise à jour du tableau, on peut s’y prendre comme suit :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    private static IDictionary<string, T> GetValuesForThread(Thread thread)
{
// Find the TLS for the specified thread
var query = from entry in _tls

// Only get threads that are still alive
let t = entry.T.Target as Thread

// Get the requested thread
where t != null && t == thread
select entry.U;

var localStorage = query.FirstOrDefault();

if (localStorage == null)
{
bool success = false;

// The storage for the new Thread
localStorage = new Dictionary<string, T>();

while(!success)
{
// store the original array so we can check later if there has not
// been anyone that has updated the array at the same time we did
var originalTls = _tls;

var newTls = new List<Tuple<WeakReference, IDictionary<string, T>>>();

// Add the slots for which threads references are still alive
newTls.AddRange(_tls.Where(t => t.T.IsAlive));

var newSlot = new Tuple<WeakReference, IDictionary<string, T>>()
{
T = new WeakReference(thread),
U = localStorage
};

newTls.Add(newSlot);

// If no other thread has changed the array, replace it.
success = Interlocked.CompareExchange(ref _tls, newTls.ToArray(), originalTls) != _tls;
}
}

return localStorage;
}


L’utilisation d’une approche sans lock comme celle-ci devrait limiter la contention autour de l’utilisation de cette classe de simili-TLS. Il pourrait y avoir, de temps à autres, des manipulations faites plusieurs fois lors de “race conditions” sur la mise à jour de la variable _tls, mais cela est tout à fait acceptable. De plus, les livelocks ne peuvent pas arriver, considérant le type de système préemptif sur lequel WP7 fonctionne.

J’ai l’impression que développer sur cette plateforme va nécessiter plein de petits “hacks” du genre... Ca va être intéressant !

[VS2010] Power Tools : Comment désactiver le Ctrl+Click Aller à la Définition

La semaine dernière, Microsoft a publié les Visual Studio 2010 Productivity Power Tool Extensions, qui incluent un grand nombre de fonctionnalités qui auraient probablement dues se retrouver dans la RTM de VS2010, mais ne l’ont pas été.

A installer, vraiment. Rien que pour la fenêtre “Add Reference” qui inclus un filtre de recherche, qui fait gagner beaucoup de temps.

Mais il y a aussi une autre fonctionnalité, le Ctrl+Click Aller à la définition qui permet d’aller à la définition d’un type simplement avec un click gauche (La touche F12 dans les association clavier par défaut).

Si vous êtes comme moi, vous êtes probablement suffisamment paresseux pour laisser l’éditeur de texte sélectionner des mots complets, et vous utilisez probablement Ctrl + Click gauche pour sélectionner des mots de manière à ne pas avoir a viser trop avec la souris. Cette nouvelle extension entre en conflit direct avec la sélection de mots, et on finit par tout le temps aller à la définition des types alors que l’on veut sélectionner du texte... Et c’est très agaçant.

Comme il ne semble pas y avoir de moyen de désactiver cette fonctionnalité depuis l’IDE, on peut y aller à la dure :

  1. Allez dans C:\Users\USER_NAME\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visual Studio 2010 Pro Power Tools\10.0.10602.2200
  2. Supprimez ou renommez le fichier GoToDefProPack.dll.
  3. Retrouvez votre sélection de mots complets !

Have fun :)

[LINQ] Trouver le nom de fichier suivant disponible

Posté le jeudi 10 juin 2010 23:05 par jay :: 2 commentaire(s)
Classé sous , ,

This article is available in english.

Parfois, les exemples les plus simples sont les meilleurs.

Mettons que vous avez un fichier de configuration, et que vous voulez en faire une copie avant de le modifier. Facile, vous copiez le fichier en “filename.bak”. Mais que se passe-t-il si ce fichier existe déjà ? Soit vous l’écrasez, soit vous créez un nom de fichier auto-incrémenté.

Si l’on veut faire ce dernier, il est possible de le faire avec une boucle for. Mais comme vous êtes un bon programmeur fonctionnel, vous allez le faire en utilisant LINQ.

On peut donc écrire ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public static string CreateNewFileName(string filePath)
{
if (!File.Exists(filePath))
return filePath;

// On évite de faire cela pour tous les fichiers
var name = Path.GetFileNameWithoutExtension(filePath);
var extension = Path.GetExtension(filePath);

// Maintenant on cherche le fichier suivant
var fileQuery = from index in Enumerable.Range(2, 10000)

// On construit le nom
let fileName = string.Format("{0} ({1}){2}", name, index, extension)

// Est-ce que le fichier existe ?
where !File.Exists(fileName)

// Non ? On le sélectionne.
select fileName;

// On retourne le premier.
return fileQuery.First();
}

Il faut noter l'utilisation du mot clé “let” qui permet de réutiliser ce que l’on appelle une “range variable”. Dans ce cas, cela permet d’éviter d’appeler string.Format plusieurs fois.

 

Le cas de l’infini

Il y a malgré tout un petit problème dans cette implémentation, qui prend la forme du “10000” arbitraire. Cela peut-être tout à fait correct si vous n’avez pas l’intention de faire 10.000 fichiers de backup. Mais si c’est le cas, pour lever cette limite, on peut écrire l’itérateur suivant :

1
2
3
4
5
6
7
    public static IEnumerable<int> InfiniteRange(int start)
{
while(true)
{
yield return start++;
}
}


Qui fait en sorte de retourner une nouvelle valeur à chaque fois qu’une est demandée. Pour utiliser cette méthode, il faut bien être sur d’avoir une condition de sortie (le fichier n’existe pas, dans l’exemple précédent), sinon vous pourriez bien énumérer jusqu'à la fin des temps... En fait jusqu'à int.MaxValue, pour les nit-pickers, mais .NET 4.0 propose System.Numerics.BigInteger pour être bien sur d’arriver à la fin de temps. On ne sait jamais.

Enfin, pour utiliser cet itérateur, il faut simplement replacer :

1
        var fileQuery = from index in Enumerable.Range(2, 10000)

par

1
        var fileQuery = from index in InfiniteRange(2)

Et c’est terminé !

Notes sur une migration de WSS 3.0 vers SharePoint Foundation 2010

This post is available in english.

J’ai récemment mis à jour une ferme WSS 3.0 à Sharepoint 2010, et j’ai pensé partager quelques notes et problèmes que j’ai rencontrés durant la mise à jour.

Mon environnement est construit autour de deux VM Windows Server 2008 R2 64 Bits hébergées par Hyper-V Server R2, une VM pour le “FrontEnd” Sharepoint, et une autre pour la base de données (SQL Server 2008 SP1 64 Bits) et Search Server Express.


Mise à jour assistée par Hyper-V

Avoir l’environnement construit sur Hyper-V m’a permis de sauver beaucoup de temps, principalement par l’utilisation des “Snapshots” pris simultanément sur les deux machines. Cela a beaucoup aidé pour faire de l’expérimentation directement sur le système de production durant un “Downtime” attendu par les utilisateurs.

Les “Snapshots” permettent d’adopter une approche essai-erreur qui mène à un environnement presque “parfait”, où les erreurs peuvent être renversées très facilement. La mise à jour de Sharepoint en utilisant cette technique n’est cependant praticable que si l’espace disque est suffisant et que les bases de données de contenu Sharepoint sont de taille raisonnable.

 

Pré-requis

Voici les étapes que j’ai effectuées pour préparer l’environnement :

  • Création de clones des deux VM dans une library, juste pour être certain de pouvoir récupérer l’environnement au cas ou Hyper-V ne déteriore les VMs (on ne sait jamais)
  • Mise à jour WSS 3.0 avec la derniere mise à jour cumulative (KB978396)
  • Téléchargé les packages Sharepoint Foundation and Search Server Express
  • Installé les prérequis de Sharepoint Foundation sur les deux VM (pas ceux de Search Server, qui ne s’installent pas correctement, et qui visuellement semble installer la meme chose que le package de SPF)
  • Installé les mise à jours cumulatives de SQL Server 2008 SP1 KB970315 et KB976761 (dans cet ordre)

C’est la partie facile, où les mises à jour n’affectent pas la ferme WSS 3.0.

J’ai pris un snapshot à ce point pour éviter de recommencer ces étapes.

 

Mise à jour de SharePoint et Search Server

Vous pouvez aussi lire ces informations sur la migration sur Technet, qui sont assez bien détaillées.

Maintenant, pour la mise à jour de Sharepoint, j’ai :

  • Placé un verrou sur la collection de sites (Juste au cas ou un utilisateur viendrait mettre à jour du contenu qu’il pourrait perdre)
  • Détaché les base de données de contenu : (Voir plus loin pour l’explication de cette étape)
    • stsadm.exe -o deletecontentdb -url http://site –databasename WSS_Content
  • Sauvegardé les bases de données de contenu pour les convertir à SPF 2010 sur un autre environnement SPF2010 fraichement installé.
  • Executé le setup de Search Server Express sur les deux machines, sans lancer la configuration
  • Executé le setup de Sharepoint Foundation, sans lancer la configuratio
  • Sur la VM du “FrontEnd” (pour que le site d’administation puisse suivre les tâches de mise à jour), executé l’assistant de configuration Sharepoint. J’ai selectionné la mise à jour des styles visual pour les templates de la collection de site utilise les nouveaux styles visuels. (Ribbon powered !)
  • Après la fin de la configuration de la VM “FrontEnd”, lancé le même assistant de configuration sur la VM de base de données
  • Laissé les tâches s’exécuter et terminer.
  • Pendant ce temps, sur un environnement SPF2010 monté sur une autre VM, j’ai remonté la sauvegarde des base de données de contenu et executé cette commande PowerShell :
    • Mount-SPContentDatabase -Name WSS_Content -DatabaseServer db.server.com -WebApplication http://site –Updateuserexperience
    • Vous aurez probablement à installer les templates utilisées par l’environnement de production pour faire la mise à jour correctement.
  • Après  que la mise à jour des bases de données de contenu se soit terminée, j’ai détaché les bases de données (attention aux dépendances entre bases de données sur il y en a plus d’une) en utilisant le site d’administration SPF2010.
  • Sauvegardé les base de données de contenu migrées
  • Sur la ferme de production, supprimé et recréé les Applications Web sans collection de sites. J’ai fait cette étape pour être certain que les sites et Pool d’Applications soient configurés proprement.
  • Restauré et attaché les bases de données de contenu sur l’environnement de production en utilisant le site d’administration SPF2010.

J’ai utilisé la technique “Attacher/Détacher” parceque cela permet de faire des mises à jour parallèles de  base de données de contenu, et aussi parceque la mises à jour “In-Place” de SPF2010 n’a pas fonctionné proprement. Les librairies d’images n’ont pas été mises à jour proprement (les images ne s’affichaient pas), et que les pages par défaut ne s’affichaient pas non plus correctement, pour une raison obscure.

 

Quelques notes additionnelles

  • J’ai rencontré quelques autre problèmes lié au SSP de recherche, ou j’ai eu besoin de supprimer le SSP de Search Server et de le recréer en entier pour faire disparaitre cette erreur :

CoreResultsWebPart::OnInit: Exception initializing: System.NullReferenceException

  • Le SSP de Search Server a besoin du “Security Token Service Application”, qui utilise par défaut le paramètre “Sécurité Etendue”, qui a besoin d’être supprimé dans IIS.
  • Parceque mon setup utilise Search Server Express, les bases de données de contenu ne doivent pas sélectionner le “Search Provider” nommé “Sharepoint Foundation Search Server” pour que la recherche fonctionne proprement.

Mise à jour des Wikis

Vous constaterez aussi que l’éditeur de Wiki a été grandement amélioré, et que vous le trouverez encore plus intéressant quand vous aurez sélectionné l’option “Convertir en XHTML” dans le menu “Html du ruban. Les pages originales venant de WSS3.0 utilisent HTML 4.0 loose, HTML qui ne marche pas très bien avec le nouvel éditeur.

Mise à Jour des Forums de Discussion

J’ai également eu quelques Forums de discussion qui ont eu des problèmes de vues lors de la visualisation de conversation. Les conversation étaient affichées en utilisant la vue “Sujet” au lieu de “Threaded”, qui n’est pas application pour voir le contenu des discussion. Pour corriger cela, il suffit simplement de créer une nouvelle vue “Sujet” et de supprimer la précédente.

 

C’est à peu près tout !

Happy SharePointing ! Maintenant je peux retourner à mes expérimentations sur l’Immuabilité et F# :)

[VS2010] Configurer l'analyse de code pour toute la solution

Posté le samedi 6 mars 2010 19:23 par jay :: 0 commentaire(s)
Classé sous ,

This article is available in english.

Avec Visual Studio 2008, configurer l’analyse de code n’est pas simple. Si vous avez plus de 10 projets, cela peut prendre un bon moment pour gérer et appliquer un seul jeu de règles pour toute la solution. Il fallait donc mettre à jour tous les projets à la main, ou utiliser un outil qui éditerait les fichiers csproj pour appliquer le même jeu de règles.

Ce n’est pas vraiment plaisant à faire, ni efficace. En particulier lorsqu’il s’agit d’une centaine de projets.

Dans Visual Studio 2010, l’équipe du produit a ajouté deux choses :

  1. Les règles sont maintenant dans des fichiers externes, et ne sont plus intégrées dans le fichier de projet. Cela permet de réutiliser les règles dans les autres projets de la solution. Cool.
  2. Il y a une nouvelle section dans les propriétés de la solution, nommée “Code Analysis Settings”, qui permet d’appliquer un fichier de règles à un projet ou mieux, à tous les projets ! Très cool.

Cette option est aussi disponible via le menu “Analyze”, avec “Configure Code Analysis for Solution”.

Un petit truc à savoir cela dit : pour être capable de sélectionner tous les projets, Ctrl+A ne fonctionne pas. Il faut sélectionner le premier, puis maintenir Ctrl et sélectionner le dernier. Peut-être que l’équipe produit corrigera cela pour la version finale...

Migrer les règles depuis VS2008

Si vous migrez une solution depuis VS2008, et que vous utilisiez l’analyse de code, vous remarquerez probablement que le convertisseur de projet génère un fichier nommé “Migrated rules for MyProject.ruleset” pour chacun des projets de la solution. C’est intéressant si tous les projets n’ont pas les mêmes règles. Mais si elles sont toutes les mêmes, il faudrait toutes les gérer...

Comme tous les programmeurs, je suis paresseux, et j’ai écrit une petite macro qui enlève tous les fichiers “ruleset” générés pour la solution courante. Ma solution n’utilise alors plus qu’un seul jeu de règles.

Ce n'est pas une macro très efficace, mais puisqu'elle ne va pas être beaucoup utilisée... Vous arriverez certainement à vivre avec performance médiocre et un mauvais code VB.NET :)

La voici :

Sub RemoveAllRuleset()

    For Each project As Project In DTE.Solution.Projects

        FindRuleSets(project)

    Next

End Sub

Sub FindRuleSets(ByVal project As Project)

    For Each item As ProjectItem In project.ProjectItems

        If Not item.SubProject Is Nothing Then

            If Not item.SubProject.ProjectItems Is Nothing Then

                FindRuleSets(item.SubProject)

            End If

        End If

        If Not item.SubProject Is Nothing Then

            If Not item.SubProject.ProjectItems Is Nothing Then

                Dim ruleSets As List(Of ProjectItem) = New List(Of ProjectItem)

                For Each subItem In item.SubProject.ProjectItems

                    If subItem.Name.StartsWith("Migrated rules for ") Then

                        ruleSets.Add(subItem)

                    End If

                Next

                For Each ruleset In ruleSets

                    ruleset.Remove()

                Next

            End If

        End If

    Next

End Sub


Plus de Messages Page suivante »

Les 10 derniers blogs postés

- SharePoint : Bug sur la gestion des permissions et la synchronisation Office par Blog Technique de Romelard Fabrice le 07-10-2014, 11:35

- SharePoint 2007 : La gestion des permissions pour les Workflows par Blog Technique de Romelard Fabrice le 07-08-2014, 11:27

- TypeMock: mock everything! par Fathi Bellahcene le 07-07-2014, 17:06

- Coding is like Read par Aurélien GALTIER le 07-01-2014, 15:30

- Mes vidéos autour des nouveautés VS 2013 par Fathi Bellahcene le 06-30-2014, 20:52

- Recherche un passionné .NET par Tkfé le 06-16-2014, 12:22

- [CodePlex] Projet KISS Workflow Foundation lancé par Blog de Jérémy Jeanson le 06-08-2014, 22:25

- Etes-vous yOS compatible ? (3/3) : la feuille de route par Le blog de Patrick [MVP SharePoint] le 06-06-2014, 00:30

- [MSDN] Utiliser l'approche Contract First avec Workflow Foundation 4.5 par Blog de Jérémy Jeanson le 06-05-2014, 21:19

- [ #ESPC14 ] TH10 Moving mountains with SharePoint ! par Le blog de Patrick [MVP SharePoint] le 06-01-2014, 11:30