BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Actualités Le modèle de threading de Windows Runtime - 1ère partie

Le modèle de threading de Windows Runtime - 1ère partie

Durant la conférence Build 2013, Marytn Lovell a révélé certains des mécanismes internes du modèle de threading de WinRT. Si les développeurs .NET risquent d'être surpris par sa complexité, en particulier lorsque de multiples fenêtres sont en jeu, les développeurs COM traditionnels seront enchantés d'apprendre qu'il est beaucoup plus simple que ce dont ils ont l'habitude.

Au sujet du multithreading dans WinRT, Marytn souhaite que les développeurs retiennent les points-clés suivants :

  • Utiliser async pour les opérations de longue durée
  • Utiliser des objets "agiles" pour tout sauf les objets UI
  • Ne jamais bloquer le thread UI

Le threading du point de vue de l'application

L'application WinRT voit le monde de façon basique : il est fait de threads UI et de threads non-UI. Les objets UI "vivent" sur un thread UI particulier, alors que la plupart des autres objets peuvent être utilisés sur n'importe quel thread. Ceci constitue une différence majeure avec la conception des "clients lourds" COM, qui présentent une grande variété de types de threads et de modèles de threading.

Une autre différence entre WinRT et COM est la durée de vie des objets. À l'exception des objets UI, il est permis de créer des objets depuis n'importe quel thread, de les utiliser depuis n'importe quel thread et, si on utilise C++, de les détruire depuis n'importe quel thread. Ceci contraste avec COM, où les objets sont liés au thread qui les a créés. Pour les déplacer sur un autre thread, il est nécessaire d'invoquer des instructions spécifiques.

Pourquoi les objets UI sont-ils spéciaux ?

Autour des années 2000, Microsoft avait créé un Framework UI multithread, dans lequel aucun objet UI n'était spécial et pouvait être créé et utilisé depuis n'importe quel thread. Du point de vue théorique, cela semblait être une très bonne idée. Mais quand il a réellement fallu développer des programmes, cela a résulté en de sérieux problèmes. Par exemple, imaginez que l'utilisateur appuie sur un bouton Ok et, pendant que l'évènement Ok_Clicked est en cours de traitement, l'utilisateur appuie sur le bouton Cancel. Vous ne voulez certainement pas que Ok_Clicked et Cancel_Clicked soient exécutés en même temps. Vous ne voulez pas non plus écrire des mécanismes de verrouillage au niveau de chaque gestionnaire d'évènement pour faire en sorte que les traitements se bloquent les uns les autres. Pour résumer, il est naturel que les interactions UI soient séquencées.

De multiples threads UI

Il est tentant de penser qu'une application n'a qu'un seul thread UI, son Main, c’est-à-dire son thread de point d'entrée. En fait, l'application peut avoir de multiples threads UI, servant des objectifs différents. Dans WinRT 8, un thread UI est lancé pour prendre en charge l'interface de gestion des contrats (Contracts UI) pour le partage de contenu entre applications.

WinRT 8.1 offre en plus la possibilité d'afficher de multiples vues, chacune dans sa propre fenêtre. Chacune de ces vues aura son propre thread UI.

Il faut bien noter que le thread UI principal existera toujours pendant toute la durée de vie de l'application, même s'il n'est pas utilisé pour de l'affichage. Ce cas peut se présenter si l'application a été lancée seulement pour afficher une fenêtre de dialogue pour le partage de contenu (un Share Dialog). Donc si vous avez du code qui a besoin de vivre sur un thread unique, vous êtes censés le placer sur Main.

La réentrance

Sur un client lourd COM, il existe le concept de réentrance. Quand un appel COM sortant est fait, il est possible qu'un autre appel arrive en entrée et qu'il prenne la main sur le thread. Cela donne l'illusion de deux threads s'exécutant sur l'UI de façon concurrente. Généralement, ce n'est pas le comportement qu'on attend. Le pire, c'est qu'il n'est pas toujours évident que c'est un appel COM sortant, et donc l'activation de la pompe à messages, qui est à l'origine de la situation.

Les programmeurs Visual Basic 6 et WinForms se souviennent certainement des problèmes liés à la fonction DoEvents, qui, en activant la pompe à messages, rend temporairement l'interface réactive quand la prise en charge d'un appel prend du temps. La réentrance revient à éparpiller ces appels à DoEvents partout dans votre code, en espérant que rien de grave n'arrive. Et généralement, on ne peut pas faire autrement, car les objets COM ont besoin que la pompe à messages soit activée pour qu'ils fonctionnent.

Sur cette illustration, vous pouvez voir comment un second appel peut interrompre un premier appel lorsque la pompe à messages entre en action.

Avec WinRT 8.1, la réentrance "sans contrainte" n'est plus possible. Seuls les threads "connectés de façon logique" ont la possibilité de faire des appels au thread UI. Tous les autres messages seront bloqués en attendant que l'appel sortant soit terminé.

Cette seconde illustration montre que l'appel 2 n'est pas redistribué tant que l'appel 1 n'est pas terminé.

Les deadlocks dans les threads ASTA

En introduisant le support de multiples fenêtres dans WinRT 8.1, c'est aussi la possibilité d'avoir de multiples threads UI qui a été introduite. Le risque de deadlock apparait lorsque deux threads effectuent des appels sortants au même moment. Puisque chacun est bloqué en attente de son retour, il y a un deadlock potentiel.

Ce genre de scénario requérant un timing très serré et difficile à reproduire, le déceler pendant une session de débogage est quasiment impossible. Microsoft a donc choisi une approche différente : un appel créant potentiellement un deadlock échoue immédiatement à son invocation. Le système n'attend pas qu'un deadlock arrive, il fait en sorte qu'un mauvais code échoue à coup sûr. Pour résumer, un thread ASTA (c’est-à-dire un thread UI) ne peut pas appeler un autre thread ASTA.

Cela ne veut pas dire que les communications entre vues ne sont pas possibles. Une vue a la possibilité d'envoyer des messages à une autre vue en utilisant son dispatcher. Ou, en mettant en place une conception plus sophistiquée, il est possible de communiquer en créant un objet async vivant dans le thread pool.

Restrictions liées au threading imposées par l'OS

Une application client lourd n'est pas tenue de répondre aux messages envoyés périodiquement par le système d'exploitation qui lui demandent si elle est toujours en vie ou pas. Cependant, elle a intérêt à le faire car une application non réactive n'est pas une bonne chose du point de vue de l'utilisateur. L'OS marquera alors l'application comme telle et la grisera dans le gestionnaire de tâches.

Dans le monde de WinRT, les règles sont beaucoup plus restrictives. Si une application reste non réactive pendant trop longtemps à cause d'un thread UI occupé, l'application sera interrompue. Ainsi, il est vital que les applications effectuent leurs opérations longues en tâche de fond. Cela signifie généralement qu'il faut utiliser des primitives sans risque pour l'UI (présentées ci-dessous) plutôt que des primitives de verrouillage comme les mutex ou les sémaphores :

  • C++ : create_task
  • VB/C# : await
  • JS : promise

Une autre construction courante à éviter est Task.Wait. En C++, appeler wait sur une tâche depuis le thread UI lève immédiatement une erreur. Cette protection n'a pas encore été introduite en .Net mais, au vu du nombre d'applications interrompues à cause de cette erreur, il est probable qu'elle le soit prochainement.

Le threading par environnement

WinJS/HTML

En environnement HTML, tous les threads sont des threads UI, même ceux utilisés par les "web workers". Le thread pool existe mais il n'est pas accessible depuis du code JavaScript. Donc, si vous avez besoin d'utiliser le thread pool, vous devrez déléguer le travail à un autre langage, comme C++ par exemple.

Une particularité utile de WinJS est que les callbacks et les évènements retournent toujours leur résultat au thread source. En comparaison, en .Net, à moins que le mot clé async soit utilisé, il faut manuellement marshaler le retour au thread UI.

XAML

En environnement XAML (C++ ou .NET), tous les objets XAML sont liés à leur thread. De plus, tous les objets XAML au sein d'un même arbre visuel doivent être hébergés dans un thread unique. Ceci signifie que vous ne pouvez pas créer un contrôle sur une vue et le déplacer vers une vue d'une fenêtre différente.

En comparaison, cette restriction n'existait pas dans le monde Win32. Selon ce modèle, un hwnd (window handle) aurait la possibilité d'être le parent d'un autre hwnd sur un thread séparé.

DirectX

Marytn n'est pas entré dans les détails au sujet du modèle DirectX plus que de mentionner que le modèle est similaire à celui de XAML à quelques points additionnels près.

Les informations présentées dans cet article viennent de la session de la Build 2013 intitulée Windows Runtime Internals : Understanding the Threading Model. Le résumé des points clés par InfoQ sera proposé dans une deuxième partie.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT